diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/client/netmonitor/src/components/request-list/RequestListItem.js | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/components/request-list/RequestListItem.js b/devtools/client/netmonitor/src/components/request-list/RequestListItem.js new file mode 100644 index 0000000000..5c70b44e00 --- /dev/null +++ b/devtools/client/netmonitor/src/components/request-list/RequestListItem.js @@ -0,0 +1,412 @@ +/* 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("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + fetchNetworkUpdatePacket, + propertiesEqual, +} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); +const { + PANELS, + RESPONSE_HEADERS, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +// Components +/* global + RequestListColumnInitiator, + RequestListColumnContentSize, + RequestListColumnCookies, + RequestListColumnDomain, + RequestListColumnFile, + RequestListColumnMethod, + RequestListColumnProtocol, + RequestListColumnRemoteIP, + RequestListColumnResponseHeader, + RequestListColumnScheme, + RequestListColumnSetCookies, + RequestListColumnStatus, + RequestListColumnTime, + RequestListColumnTransferredSize, + RequestListColumnType, + RequestListColumnUrl, + RequestListColumnWaterfall, + RequestListColumnPriority +*/ +loader.lazyGetter(this, "RequestListColumnInitiator", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnInitiator.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnContentSize", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnContentSize.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnCookies", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnCookies.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnDomain", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnDomain.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnFile", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnFile.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnUrl", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnUrl.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnMethod", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnMethod.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnProtocol", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnProtocol.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnRemoteIP", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnRemoteIP.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnResponseHeader", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnResponseHeader.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnTime", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnTime.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnScheme", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnScheme.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnSetCookies", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnSetCookies.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnStatus", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnStatus.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnTransferredSize", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnTransferredSize.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnType", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnType.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnWaterfall", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnWaterfall.js") + ); +}); +loader.lazyGetter(this, "RequestListColumnPriority", function () { + return createFactory( + require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnPriority.js") + ); +}); + +/** + * Used by shouldComponentUpdate: compare two items, and compare only properties + * relevant for rendering the RequestListItem. Other properties (like request and + * response headers, cookies, bodies) are ignored. These are very useful for the + * network details, but not here. + */ +const UPDATED_REQ_ITEM_PROPS = [ + "mimeType", + "eventTimings", + "securityState", + "status", + "statusText", + "fromCache", + "isRacing", + "fromServiceWorker", + "method", + "url", + "remoteAddress", + "cause", + "contentSize", + "transferredSize", + "startedMs", + "totalTime", + "requestCookies", + "requestHeaders", + "responseCookies", + "responseHeaders", + "waitingTime", + "isEventStream", + "priority", +]; + +const UPDATED_REQ_PROPS = [ + "firstRequestStartedMs", + "index", + "networkDetailsOpen", + "isSelected", + "isVisible", + "requestFilterTypes", +]; + +/** + * Used by render: renders the given ColumnComponent if the flag for this column + * is set in the columns prop. The list of props are used to determine which of + * RequestListItem's need to be passed to the ColumnComponent. Any objects contained + * in that list are passed as props verbatim. + */ +const COLUMN_COMPONENTS = [ + { column: "status", ColumnComponent: RequestListColumnStatus }, + { column: "method", ColumnComponent: RequestListColumnMethod }, + { + column: "domain", + ColumnComponent: RequestListColumnDomain, + props: ["onSecurityIconMouseDown"], + }, + { + column: "file", + ColumnComponent: RequestListColumnFile, + props: ["onWaterfallMouseDown"], + }, + { + column: "url", + ColumnComponent: RequestListColumnUrl, + props: ["onSecurityIconMouseDown"], + }, + { column: "protocol", ColumnComponent: RequestListColumnProtocol }, + { column: "scheme", ColumnComponent: RequestListColumnScheme }, + { column: "remoteip", ColumnComponent: RequestListColumnRemoteIP }, + { + column: "initiator", + ColumnComponent: RequestListColumnInitiator, + props: ["onInitiatorBadgeMouseDown"], + }, + { column: "type", ColumnComponent: RequestListColumnType }, + { + column: "cookies", + ColumnComponent: RequestListColumnCookies, + props: ["connector"], + }, + { + column: "setCookies", + ColumnComponent: RequestListColumnSetCookies, + props: ["connector"], + }, + { column: "transferred", ColumnComponent: RequestListColumnTransferredSize }, + { column: "contentSize", ColumnComponent: RequestListColumnContentSize }, + { column: "priority", ColumnComponent: RequestListColumnPriority }, + { + column: "startTime", + ColumnComponent: RequestListColumnTime, + props: ["connector", "firstRequestStartedMs", { type: "start" }], + }, + { + column: "endTime", + ColumnComponent: RequestListColumnTime, + props: ["connector", "firstRequestStartedMs", { type: "end" }], + }, + { + column: "responseTime", + ColumnComponent: RequestListColumnTime, + props: ["connector", "firstRequestStartedMs", { type: "response" }], + }, + { + column: "duration", + ColumnComponent: RequestListColumnTime, + props: ["connector", "firstRequestStartedMs", { type: "duration" }], + }, + { + column: "latency", + ColumnComponent: RequestListColumnTime, + props: ["connector", "firstRequestStartedMs", { type: "latency" }], + }, +]; + +/** + * Render one row in the request list. + */ +class RequestListItem extends Component { + static get propTypes() { + return { + blocked: PropTypes.bool, + connector: PropTypes.object.isRequired, + columns: PropTypes.object.isRequired, + item: PropTypes.object.isRequired, + index: PropTypes.number.isRequired, + isSelected: PropTypes.bool.isRequired, + isVisible: PropTypes.bool.isRequired, + firstRequestStartedMs: PropTypes.number.isRequired, + fromCache: PropTypes.bool, + networkActionOpen: PropTypes.bool, + networkDetailsOpen: PropTypes.bool, + onInitiatorBadgeMouseDown: PropTypes.func.isRequired, + onDoubleClick: PropTypes.func.isRequired, + onDragStart: PropTypes.func.isRequired, + onContextMenu: PropTypes.func.isRequired, + onFocusedNodeChange: PropTypes.func, + onMouseDown: PropTypes.func.isRequired, + onSecurityIconMouseDown: PropTypes.func.isRequired, + onWaterfallMouseDown: PropTypes.func.isRequired, + requestFilterTypes: PropTypes.object.isRequired, + selectedActionBarTabId: PropTypes.string, + intersectionObserver: PropTypes.object, + }; + } + + componentDidMount() { + if (this.props.isSelected) { + this.refs.listItem.focus(); + } + if (this.props.intersectionObserver) { + this.props.intersectionObserver.observe(this.refs.listItem); + } + + const { connector, item, requestFilterTypes } = this.props; + // Filtering XHR & WS require to lazily fetch requestHeaders & responseHeaders + if (requestFilterTypes.xhr || requestFilterTypes.ws) { + fetchNetworkUpdatePacket(connector.requestData, item, [ + "requestHeaders", + "responseHeaders", + ]); + } + } + + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillReceiveProps(nextProps) { + const { connector, item, requestFilterTypes } = nextProps; + // Filtering XHR & WS require to lazily fetch requestHeaders & responseHeaders + if (requestFilterTypes.xhr || requestFilterTypes.ws) { + fetchNetworkUpdatePacket(connector.requestData, item, [ + "requestHeaders", + "responseHeaders", + ]); + } + } + + shouldComponentUpdate(nextProps) { + return ( + !propertiesEqual( + UPDATED_REQ_ITEM_PROPS, + this.props.item, + nextProps.item + ) || + !propertiesEqual(UPDATED_REQ_PROPS, this.props, nextProps) || + this.props.columns !== nextProps.columns + ); + } + + componentDidUpdate(prevProps) { + if (!prevProps.isSelected && this.props.isSelected) { + this.refs.listItem.focus(); + if (this.props.onFocusedNodeChange) { + this.props.onFocusedNodeChange(); + } + } + } + + componentWillUnmount() { + if (this.props.intersectionObserver) { + this.props.intersectionObserver.unobserve(this.refs.listItem); + } + } + + render() { + const { + blocked, + connector, + columns, + item, + index, + isSelected, + isVisible, + firstRequestStartedMs, + fromCache, + networkActionOpen, + onDoubleClick, + onDragStart, + onContextMenu, + onMouseDown, + onWaterfallMouseDown, + selectedActionBarTabId, + } = this.props; + + const classList = ["request-list-item", index % 2 ? "odd" : "even"]; + isSelected && classList.push("selected"); + fromCache && classList.push("fromCache"); + blocked && classList.push("blocked"); + + return dom.tr( + { + ref: "listItem", + className: classList.join(" "), + "data-id": item.id, + draggable: + !blocked && + networkActionOpen && + selectedActionBarTabId === PANELS.BLOCKING, + tabIndex: 0, + onContextMenu, + onMouseDown, + onDoubleClick, + onDragStart, + }, + ...COLUMN_COMPONENTS.filter(({ column }) => columns[column]).map( + ({ column, ColumnComponent, props: columnProps }) => { + return ColumnComponent({ + key: column, + item, + ...(columnProps || []).reduce((acc, keyOrObject) => { + if (typeof keyOrObject == "string") { + acc[keyOrObject] = this.props[keyOrObject]; + } else { + Object.assign(acc, keyOrObject); + } + return acc; + }, {}), + }); + } + ), + ...RESPONSE_HEADERS.filter(header => columns[header]).map(header => + RequestListColumnResponseHeader({ + connector, + item, + header, + }) + ), + // The last column is Waterfall (aka Timeline) + columns.waterfall && + RequestListColumnWaterfall({ + connector, + firstRequestStartedMs, + item, + onWaterfallMouseDown, + isVisible, + }) + ); + } +} + +module.exports = RequestListItem; |