Bug 1559398 - Implement table and preview sections in WebSocket side panel. r=Honza

Implement table and preview sections in WebSocket side panel.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
tanhengyeow 2019-06-28 07:24:59 +00:00
Родитель fb05c2c381
Коммит 44c6145f6e
25 изменённых файлов: 957 добавлений и 123 удалений

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

@ -637,6 +637,34 @@ netmonitor.toolbar.contentSize=Size
# in the network table toolbar, above the "waterfall" column.
netmonitor.toolbar.waterfall=Timeline
# LOCALIZATION NOTE (netmonitor.ws.toolbar.frameType): This is the label displayed
# in the websocket frame table header, above the "type" column.
netmonitor.ws.toolbar.frameType=Type
# LOCALIZATION NOTE (netmonitor.ws.toolbar.size): This is the label displayed
# 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.opCode): This is the label displayed
# in the websocket frame table header, above the "opCode" column.
netmonitor.ws.toolbar.opCode=OpCode
# LOCALIZATION NOTE (netmonitor.ws.toolbar.maskBit): This is the label displayed
# in the websocket frame table header, above the "maskBit" column.
netmonitor.ws.toolbar.maskBit=MaskBit
# LOCALIZATION NOTE (netmonitor.ws.toolbar.finBit): This is the label displayed
# in the websocket frame table header, above the "finBit" column.
netmonitor.ws.toolbar.finBit=FinBit
# LOCALIZATION NOTE (netmonitor.ws.toolbar.time): This is the label displayed
# in the websocket frame table header, above the "time" column.
netmonitor.ws.toolbar.time=Time
# LOCALIZATION NOTE (netmonitor.tab.headers): This is the label displayed
# in the network details pane identifying the headers tab.
netmonitor.tab.headers=Headers

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

@ -4,7 +4,11 @@
"use strict";
const { WS_ADD_FRAME } = require("../constants");
const {
WS_ADD_FRAME,
WS_SELECT_FRAME,
WS_OPEN_FRAME_DETAILS,
} = require("../constants");
function addFrame(httpChannelId, data) {
return {
@ -14,6 +18,31 @@ function addFrame(httpChannelId, data) {
};
}
/**
* Select frame.
*/
function selectFrame(frame) {
return {
type: WS_SELECT_FRAME,
open: true,
frame,
};
}
/**
* Open frame details panel.
*
* @param {boolean} open - expected frame details panel open state
*/
function openFrameDetails(open) {
return {
type: WS_OPEN_FRAME_DETAILS,
open,
};
}
module.exports = {
addFrame,
selectFrame,
openFrameDetails,
};

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

@ -10,6 +10,10 @@
overflow-x: hidden;
}
.ws-frame-list-empty-notice {
width: 100%;
}
.empty-notice-element {
padding-top: 12px;
padding-left: 12px;
@ -56,7 +60,8 @@
overflow-y: auto;
}
.requests-list-table {
.requests-list-table,
.ws-frames-list-table {
/* Reset default browser style of <table> */
border-spacing: 0;
width: 100%;
@ -66,7 +71,8 @@
table-layout: fixed;
}
.requests-list-column {
.requests-list-column,
.ws-frames-list-column {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -82,7 +88,8 @@
/* Requests list headers */
.requests-list-headers-group {
.requests-list-headers-group,
.ws-frames-list-headers-group {
/* Avoid .devtools-toolbar to override <thead> display type */
display: table-header-group;
@ -93,16 +100,19 @@
z-index: 1;
}
.requests-list-headers {
.requests-list-headers,
.ws-frames-list-headers {
height: 24px;
padding: 0;
}
.requests-list-headers .requests-list-column:first-child .requests-list-header-button {
.requests-list-headers .requests-list-column:first-child .requests-list-header-button,
.ws-frames-list-headers .ws-frames-list-column:first-child .ws-frames-list-header-button {
border-width: 0;
}
.requests-list-header-button {
.requests-list-header-button,
.ws-frames-list-header-button {
background-color: transparent;
border-image: linear-gradient(transparent 15%,
var(--theme-splitter-color) 15%,
@ -126,7 +136,8 @@
background-color: rgba(0, 0, 0, 0.1);
}
.requests-list-header-button > .button-text {
.requests-list-header-button > .button-text,
.ws-frames-list-header-button > .button-text {
display: inline-block;
vertical-align: middle;
width: 100%;
@ -134,7 +145,8 @@
text-overflow: ellipsis;
}
.requests-list-header-button > .button-icon {
.requests-list-header-button > .button-icon,
.ws-frames-list-header-button > .button-icon {
/* display icon only when column sorted otherwise display:none */
display: none;
width: 7px;
@ -309,7 +321,8 @@
filter: brightness(500%);
}
.request-list-item .requests-list-column {
.request-list-item .requests-list-column,
.ws-frame-list-item .ws-frames-list-column {
padding-inline-start: 4px;
}
@ -464,16 +477,19 @@
/* Request list item */
.request-list-item {
.request-list-item,
.ws-frame-list-item {
height: 24px;
line-height: 24px;
}
.request-list-item:not(.selected).odd {
.request-list-item:not(.selected).odd,
.ws-frame-list-item:not(.selected).odd {
background-color: var(--table-zebra-background);
}
.request-list-item:not(.selected):hover {
.request-list-item:not(.selected):hover,
.ws-frame-list-item:not(.selected):hover {
background-color: var(--table-selection-background-hover);
}
@ -489,7 +505,8 @@
* Put ahead of .request-list-item.blocked to avoid specificity conflict.
* Bug 1530914 - Highlighted Security Value is difficult to read.
*/
.request-list-item.selected {
.request-list-item.selected,
.ws-frame-list-item.selected {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}

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

@ -18,7 +18,7 @@ const Tabbar = createFactory(require("devtools/client/shared/components/tabs/Tab
const TabPanel = createFactory(require("devtools/client/shared/components/tabs/Tabs").TabPanel);
const CookiesPanel = createFactory(require("./CookiesPanel"));
const HeadersPanel = createFactory(require("./HeadersPanel"));
const WebSocketsPanel = createFactory(require("./WebSocketsPanel"));
const WebSocketsPanel = createFactory(require("./websockets/WebSocketsPanel"));
const ParamsPanel = createFactory(require("./ParamsPanel"));
const CachePanel = createFactory(require("./CachePanel"));
const ResponsePanel = createFactory(require("./ResponsePanel"));
@ -131,6 +131,7 @@ class TabboxPanel extends Component {
},
WebSocketsPanel({
channelId,
connector,
}),
),
TabPanel({

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

@ -1,85 +0,0 @@
/* 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 } = require("devtools/client/shared/vendor/react");
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 dom = require("devtools/client/shared/vendor/react-dom-factories");
const { table, tbody, thead, tr, td, th, div } = dom;
const { L10N } = require("../utils/l10n");
const FRAMES_EMPTY_TEXT = L10N.getStr("webSocketsEmptyText");
class WebSocketsPanel extends Component {
static get propTypes() {
return {
channelId: PropTypes.number,
frames: PropTypes.array,
};
}
constructor(props) {
super(props);
}
render() {
const { frames } = this.props;
if (!frames) {
return div({ className: "empty-notice" },
FRAMES_EMPTY_TEXT
);
}
const rows = [];
frames.forEach((frame, index) => {
rows.push(
tr(
{ key: index,
className: "frames-row" },
td({ className: "frames-cell" }, frame.type),
td({ className: "frames-cell" }, frame.httpChannelId),
td({ className: "frames-cell" }, frame.payload),
td({ className: "frames-cell" }, frame.opCode),
td({ className: "frames-cell" }, frame.maskBit.toString()),
td({ className: "frames-cell" }, frame.finBit.toString()),
td({ className: "frames-cell" }, frame.timeStamp)
)
);
});
return table(
{ className: "frames-list-table" },
thead(
{ className: "frames-head" },
tr(
{ className: "frames-row" },
th({ className: "frames-headerCell" }, "Type"),
th({ className: "frames-headerCell" }, "Channel ID"),
th({ className: "frames-headerCell" }, "Payload"),
th({ className: "frames-headerCell" }, "OpCode"),
th({ className: "frames-headerCell" }, "MaskBit"),
th({ className: "frames-headerCell" }, "FinBit"),
th({ className: "frames-headerCell" }, "Time")
)
),
tbody(
{
className: "frames-list-tableBody",
},
rows
)
);
}
}
module.exports = connect((state, props) => ({
frames: getFramesByChannelId(state, props.channelId),
}))(WebSocketsPanel);

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

@ -2,6 +2,10 @@
# 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/.
DIRS += [
'websockets',
]
DevToolsModules(
'App.js',
'CachePanel.js',
@ -47,5 +51,4 @@ DevToolsModules(
'TabboxPanel.js',
'TimingsPanel.js',
'Toolbar.js',
'WebSocketsPanel.js',
)

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

@ -0,0 +1,41 @@
/* 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 } = 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");
/**
* Renders the "FinBit" column of a WebSocket frame.
*/
class FrameListColumnFinBit extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
};
}
shouldComponentUpdate(nextProps) {
return this.props.item.finBit !== nextProps.item.finBit;
}
render() {
const { finBit } = this.props.item;
const { index } = this.props;
return dom.td(
{
key: index,
className: "ws-frames-list-column ws-frames-list-finBit",
title: finBit.toString(),
},
finBit.toString()
);
}
}
module.exports = FrameListColumnFinBit;

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

@ -0,0 +1,41 @@
/* 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 } = 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");
/**
* Renders the "MaskBit" column of a WebSocket frame.
*/
class FrameListColumnMaskBit extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
};
}
shouldComponentUpdate(nextProps) {
return this.props.item.maskBit !== nextProps.item.maskBit;
}
render() {
const { maskBit } = this.props.item;
const { index } = this.props;
return dom.td(
{
key: index,
className: "ws-frames-list-column ws-frames-list-maskBit",
title: maskBit.toString(),
},
maskBit.toString()
);
}
}
module.exports = FrameListColumnMaskBit;

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

@ -0,0 +1,41 @@
/* 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 } = 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");
/**
* Renders the "OpCode" column of a WebSocket frame.
*/
class FrameListColumnOpCode extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
};
}
shouldComponentUpdate(nextProps) {
return this.props.item.opCode !== nextProps.item.opCode;
}
render() {
const { opCode } = this.props.item;
const { index } = this.props;
return dom.td(
{
key: index,
className: "ws-frames-list-column ws-frames-list-opCode",
title: opCode,
},
opCode
);
}
}
module.exports = FrameListColumnOpCode;

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

@ -0,0 +1,64 @@
/* 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 } = 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 { getFramePayload } = require("../../utils/request-utils");
/**
* Renders the "Payload" column of a WebSocket frame.
*/
class FrameListColumnPayload extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
connector: PropTypes.object.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
payload: "",
};
}
componentDidMount() {
const { item, connector } = this.props;
getFramePayload(item.payload, connector.getLongString).then(payload => {
this.setState({
payload,
});
});
}
componentWillReceiveProps(nextProps) {
const { item, connector } = nextProps;
getFramePayload(item.payload, connector.getLongString).then(payload => {
this.setState({
payload,
});
});
}
render() {
const { index } = this.props;
return dom.td(
{
key: index,
className: "ws-frames-list-column ws-frames-list-payload",
title: this.state.payload,
},
this.state.payload
);
}
}
module.exports = FrameListColumnPayload;

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

@ -0,0 +1,42 @@
/* 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 } = 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 { getFormattedSize } = require("../../utils/format-utils");
/**
* Renders the "Size" column of a WebSocket frame.
*/
class FrameListColumnSize extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
};
}
shouldComponentUpdate(nextProps) {
return this.props.item.payload !== nextProps.item.payload;
}
render() {
const { payload } = this.props.item;
const { index } = this.props;
return dom.td(
{
key: index,
className: "ws-frames-list-column ws-frames-list-size",
title: getFormattedSize(payload.length),
},
getFormattedSize(payload.length)
);
}
}
module.exports = FrameListColumnSize;

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

@ -0,0 +1,44 @@
/* 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 } = 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");
/**
* Renders the "Time" column of a WebSocket frame.
*/
class FrameListColumnTime extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
};
}
shouldComponentUpdate(nextProps) {
return this.props.item.timeStamp !== nextProps.item.timeStamp;
}
render() {
const { timeStamp } = this.props.item;
const { index } = this.props;
// Convert microseconds (DOMHighResTimeStamp) to milliseconds
const time = timeStamp / 1000;
return dom.td(
{
key: index,
className: "ws-frames-list-column ws-frames-list-time",
title: timeStamp,
},
new Date(time).toLocaleTimeString()
);
}
}
module.exports = FrameListColumnTime;

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

@ -0,0 +1,41 @@
/* 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 } = 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");
/**
* Renders the "Type" column of a WebSocket frame.
*/
class FrameListColumnType extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
};
}
shouldComponentUpdate(nextProps) {
return this.props.item.type !== nextProps.item.type;
}
render() {
const { type } = this.props.item;
const { index } = this.props;
return dom.td(
{
key: index,
className: "ws-frames-list-column ws-frames-list-type",
title: type,
},
type
);
}
}
module.exports = FrameListColumnType;

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

@ -0,0 +1,99 @@
/* 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 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
const { table, tbody, div } = dom;
const { L10N } = require("../../utils/l10n");
const FRAMES_EMPTY_TEXT = L10N.getStr("webSocketsEmptyText");
const Actions = require("../../actions/index");
const { getSelectedFrame } = require("../../selectors/index");
loader.lazyGetter(this, "FrameListHeader", function() {
return createFactory(require("./FrameListHeader"));
});
loader.lazyGetter(this, "FrameListItem", function() {
return createFactory(require("./FrameListItem"));
});
const LEFT_MOUSE_BUTTON = 0;
/**
* Renders the actual contents of the WebSocket frame list.
*/
class FrameListContent extends Component {
static get propTypes() {
return {
channelId: PropTypes.number,
connector: PropTypes.object.isRequired,
frames: PropTypes.array,
selectedFrame: PropTypes.object,
selectFrame: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
}
onMouseDown(evt, item) {
if (evt.button === LEFT_MOUSE_BUTTON) {
this.props.selectFrame(item);
}
}
render() {
const { frames, selectedFrame, connector } = this.props;
if (!frames) {
return div(
{ className: "empty-notice ws-frame-list-empty-notice" },
FRAMES_EMPTY_TEXT
);
}
return table(
{ className: "ws-frames-list-table" },
FrameListHeader(),
tbody(
{
className: "ws-frames-list-body",
},
frames.map((item, index) =>
FrameListItem({
key: "ws-frame-list-item-" + index,
item,
index,
isSelected: item === selectedFrame,
onMouseDown: evt => this.onMouseDown(evt, item),
connector,
})
)
)
);
}
}
module.exports = connect(
(state, props) => ({
selectedFrame: getSelectedFrame(state),
frames: getFramesByChannelId(state, props.channelId),
}),
dispatch => ({
selectFrame: item => dispatch(Actions.selectFrame(item)),
})
)(FrameListContent);

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

@ -0,0 +1,67 @@
/* 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 } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { WS_FRAMES_HEADERS } = require("../../constants");
const { L10N } = require("../../utils/l10n");
const { div, button } = dom;
/**
* Renders the frame list header.
*/
class FrameListHeader extends Component {
constructor(props) {
super(props);
}
/**
* Render one column header from the table headers.
*/
renderColumn(header) {
const name = header.name;
const label = L10N.getStr(`netmonitor.ws.toolbar.${name}`);
return dom.td(
{
id: `ws-frames-list-${name}-header-box`,
className: `ws-frames-list-column ws-frames-list-${name}`,
key: name,
},
button(
{
id: `ws-frames-list-${name}-button`,
className: `ws-frames-list-header-button`,
title: label,
},
div({ className: "button-text" }, label),
div({ className: "button-icon" })
)
);
}
/**
* Render all columns in the table header
*/
renderColumns() {
return WS_FRAMES_HEADERS.map(header => this.renderColumn(header));
}
render() {
return dom.thead(
{ className: "devtools-toolbar ws-frames-list-headers-group" },
dom.tr(
{
className: "ws-frames-list-headers",
},
this.renderColumns()
)
);
}
}
module.exports = FrameListHeader;

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

@ -0,0 +1,87 @@
/* 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { tr } = dom;
loader.lazyGetter(this, "FrameListColumnType", function() {
return createFactory(require("./FrameListColumnType"));
});
loader.lazyGetter(this, "FrameListColumnSize", function() {
return createFactory(require("./FrameListColumnSize"));
});
loader.lazyGetter(this, "FrameListColumnPayload", function() {
return createFactory(require("./FrameListColumnPayload"));
});
loader.lazyGetter(this, "FrameListColumnOpCode", function() {
return createFactory(require("./FrameListColumnOpCode"));
});
loader.lazyGetter(this, "FrameListColumnMaskBit", function() {
return createFactory(require("./FrameListColumnMaskBit"));
});
loader.lazyGetter(this, "FrameListColumnFinBit", function() {
return createFactory(require("./FrameListColumnFinBit"));
});
loader.lazyGetter(this, "FrameListColumnTime", function() {
return createFactory(require("./FrameListColumnTime"));
});
const COLUMN_COMPONENTS = [
{ column: "type", ColumnComponent: FrameListColumnType },
{ column: "size", ColumnComponent: FrameListColumnSize },
{ column: "payload", ColumnComponent: FrameListColumnPayload },
{ column: "opCode", ColumnComponent: FrameListColumnOpCode },
{ column: "maskBit", ColumnComponent: FrameListColumnMaskBit },
{ column: "finBit", ColumnComponent: FrameListColumnFinBit },
{ column: "time", ColumnComponent: FrameListColumnTime },
];
/**
* Renders one row in the frame list.
*/
class FrameListItem extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
isSelected: PropTypes.bool.isRequired,
onMouseDown: PropTypes.func.isRequired,
connector: PropTypes.object.isRequired,
};
}
render() {
const { item, index, isSelected, onMouseDown, connector } = this.props;
const classList = ["ws-frame-list-item", index % 2 ? "odd" : "even"];
if (isSelected) {
classList.push("selected");
}
return tr(
{
className: classList.join(" "),
tabIndex: 0,
onMouseDown,
},
COLUMN_COMPONENTS.map(({ ColumnComponent, column }) =>
ColumnComponent({
key: column + "-" + index,
connector,
item,
index,
})
)
);
}
}
module.exports = FrameListItem;

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

@ -0,0 +1,60 @@
/* 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 } = 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 { getFramePayload } = require("../../utils/request-utils");
/**
* Shows the full payload of a WebSocket frame.
* The payload is unwrapped from the LongStringActor object.
*/
class FramePayload extends Component {
static get propTypes() {
return {
connector: PropTypes.object.isRequired,
selectedFrame: PropTypes.object,
};
}
constructor(props) {
super(props);
this.state = {
payload: "",
};
}
componentDidMount() {
const { selectedFrame, connector } = this.props;
getFramePayload(selectedFrame.payload, connector.getLongString).then(
payload => {
this.setState({
payload,
});
}
);
}
componentWillReceiveProps(nextProps) {
const { selectedFrame, connector } = nextProps;
getFramePayload(selectedFrame.payload, connector.getLongString).then(
payload => {
this.setState({
payload,
});
}
);
}
render() {
return div({ className: "ws-frame-payload" }, this.state.payload);
}
}
module.exports = FramePayload;

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

@ -0,0 +1,109 @@
/* 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 Services = require("Services");
const {
Component,
createFactory,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const {
connect,
} = require("devtools/client/shared/redux/visibility-handler-connect");
const Actions = require("../../actions/index");
const {
getSelectedFrame,
isSelectedFrameVisible,
} = require("../../selectors/index");
// Components
const SplitBox = createFactory(
require("devtools/client/shared/components/splitter/SplitBox")
);
const FrameListContent = createFactory(require("./FrameListContent"));
loader.lazyGetter(this, "FramePayload", function() {
return createFactory(require("./FramePayload"));
});
/**
* Renders a list of WebSocket frames in table view.
* Full payload is separated using a SplitBox.
*/
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,
};
}
constructor(props) {
super(props);
}
componentDidUpdate() {
const { selectedFrameVisible, openFrameDetailsTab } = this.props;
if (!selectedFrameVisible) {
openFrameDetailsTab(false);
}
}
render() {
const {
frameDetailsOpen,
channelId,
connector,
selectedFrame,
} = this.props;
const initialWidth = Services.prefs.getIntPref(
"devtools.netmonitor.ws.payload-preview-width"
);
const initialHeight = Services.prefs.getIntPref(
"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,
});
}
}
module.exports = connect(
(state, props) => ({
selectedFrame: getSelectedFrame(state),
frameDetailsOpen: state.webSockets.frameDetailsOpen,
selectedFrameVisible: isSelectedFrameVisible(
state,
props.channelId,
getSelectedFrame(state)
),
}),
dispatch => ({
openFrameDetailsTab: open => dispatch(Actions.openFrameDetails(open)),
})
)(WebSocketsPanel);

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

@ -0,0 +1,18 @@
# 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/.
DevToolsModules(
'FrameListColumnFinBit.js',
'FrameListColumnMaskBit.js',
'FrameListColumnOpCode.js',
'FrameListColumnPayload.js',
'FrameListColumnSize.js',
'FrameListColumnTime.js',
'FrameListColumnType.js',
'FrameListContent.js',
'FrameListHeader.js',
'FrameListItem.js',
'FramePayload.js',
'WebSocketsPanel.js',
)

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

@ -35,6 +35,8 @@ const actionTypes = {
WATERFALL_RESIZE: "WATERFALL_RESIZE",
SET_COLUMNS_WIDTH: "SET_COLUMNS_WIDTH",
WS_ADD_FRAME: "WS_ADD_FRAME",
WS_SELECT_FRAME: "WS_SELECT_FRAME",
WS_OPEN_FRAME_DETAILS: "WS_OPEN_FRAME_DETAILS",
};
// Descriptions for what this frontend is currently doing.
@ -316,6 +318,30 @@ const FILTER_TAGS = [
"other",
];
const WS_FRAMES_HEADERS = [
{
name: "frameType",
},
{
name: "size",
},
{
name: "payload",
},
{
name: "opCode",
},
{
name: "maskBit",
},
{
name: "finBit",
},
{
name: "time",
},
];
const REQUESTS_WATERFALL = {
BACKGROUND_TICKS_MULTIPLE: 5, // ms
BACKGROUND_TICKS_SCALES: 3,
@ -444,6 +470,7 @@ const general = {
FILTER_SEARCH_DELAY: 200,
UPDATE_PROPS,
HEADERS,
WS_FRAMES_HEADERS,
RESPONSE_HEADERS,
FILTER_FLAGS,
FILTER_TAGS,

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

@ -11,14 +11,14 @@ const { sortReducer } = require("./sort");
const { filters } = require("./filters");
const { timingMarkers } = require("./timing-markers");
const { ui } = require("./ui");
const { webSocketsReducer } = require("./web-sockets");
const { webSockets } = require("./web-sockets");
const networkThrottling = require("devtools/client/shared/components/throttling/reducer");
module.exports = batchingReducer(
combineReducers({
requests: requestsReducer,
sort: sortReducer,
webSockets: webSocketsReducer,
webSockets,
filters,
timingMarkers,
ui,

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

@ -6,6 +6,8 @@
const {
WS_ADD_FRAME,
WS_SELECT_FRAME,
WS_OPEN_FRAME_DETAILS,
} = require("../constants");
/**
@ -16,32 +18,39 @@ function WebSockets() {
return {
// Map with all requests (key = channelId, value = array of frame objects)
frames: new Map(),
selectedFrame: null,
frameDetailsOpen: false,
};
}
/**
* This reducer is responsible for maintaining list of
* WebSocket frames within the Network panel.
*/
function webSocketsReducer(state = WebSockets(), action) {
switch (action.type) {
// Appending new frame into the map.
case WS_ADD_FRAME: {
const nextState = { ...state };
// Appending new frame into the map.
function addFrame(state, action) {
const nextState = { ...state };
const newFrame = {
httpChannelId: action.httpChannelId,
...action.data,
};
const newFrame = {
httpChannelId: action.httpChannelId,
...action.data,
};
nextState.frames = mapSet(state.frames, newFrame.httpChannelId, newFrame);
nextState.frames = mapSet(state.frames, newFrame.httpChannelId, newFrame);
return nextState;
}
return nextState;
}
default:
return state;
}
// Select specific frame.
function selectFrame(state, action) {
return {
...state,
selectedFrame: action.frame,
frameDetailsOpen: action.open,
};
}
function openFrameDetails(state, action) {
return {
...state,
frameDetailsOpen: action.open,
};
}
/**
@ -58,7 +67,24 @@ function mapSet(map, key, value) {
return newMap.set(key, [value]);
}
/**
* This reducer is responsible for maintaining list of
* WebSocket frames within the Network panel.
*/
function webSockets(state = WebSockets(), action) {
switch (action.type) {
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);
default:
return state;
}
}
module.exports = {
WebSockets,
webSocketsReducer,
webSockets,
};

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

@ -4,10 +4,32 @@
"use strict";
const { createSelector } = require("devtools/client/shared/vendor/reselect");
function getFramesByChannelId(state, channelId) {
return state.webSockets.frames.get(channelId);
}
/**
* 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 getSelectedFrame = createSelector(
state => state.webSockets,
({ selectedFrame }) => (selectedFrame ? selectedFrame : undefined)
);
module.exports = {
getFramesByChannelId,
getSelectedFrame,
isSelectedFrameVisible,
};

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

@ -520,6 +520,15 @@ async function updateFormDataSections(props) {
}
}
/**
* This helper function helps to resolve the full payload of a WebSocket frame
* that is wrapped in a LongStringActor object.
*/
async function getFramePayload(payload, getLongString) {
const result = await getLongString(payload);
return result;
}
/**
* This helper function is used for additional processing of
* incoming network update packets. It's used by Network and
@ -561,6 +570,7 @@ module.exports = {
getFileName,
getEndTime,
getFormattedProtocol,
getFramePayload,
getResponseHeader,
getResponseTime,
getStartTime,

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

@ -169,6 +169,8 @@ pref("devtools.netmonitor.visibleColumns",
);
pref("devtools.netmonitor.columnsData",
'[{"name":"status","minWidth":30,"width":5}, {"name":"method","minWidth":30,"width":5}, {"name":"domain","minWidth":30,"width":10}, {"name":"file","minWidth":30,"width":25}, {"name":"url","minWidth":30,"width":25}, {"name":"cause","minWidth":30,"width":10},{"name":"type","minWidth":30,"width":5},{"name":"transferred","minWidth":30,"width":10},{"name":"contentSize","minWidth":30,"width":5},{"name":"waterfall","minWidth":150,"width":25}]');
pref("devtools.netmonitor.ws.payload-preview-width", 550);
pref("devtools.netmonitor.ws.payload-preview-height", 450);
// Support for columns resizing pref is now enabled (after merge date 03/18/19).
pref("devtools.netmonitor.features.resizeColumns", true);