summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/components/request-list/RequestListItem.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/netmonitor/src/components/request-list/RequestListItem.js412
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;