RequestListItem.js (14019B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 Component, 9 createFactory, 10 } = require("resource://devtools/client/shared/vendor/react.mjs"); 11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 12 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 13 const { 14 fetchNetworkUpdatePacket, 15 propertiesEqual, 16 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 17 const { 18 PANELS, 19 RESPONSE_HEADERS, 20 } = require("resource://devtools/client/netmonitor/src/constants.js"); 21 22 // Components 23 /* global 24 RequestListColumnInitiator, 25 RequestListColumnContentSize, 26 RequestListColumnCookies, 27 RequestListColumnDomain, 28 RequestListColumnFile, 29 RequestListColumnPath, 30 RequestListColumnMethod, 31 RequestListColumnProtocol, 32 RequestListColumnRemoteIP, 33 RequestListColumnResponseHeader, 34 RequestListColumnScheme, 35 RequestListColumnSetCookies, 36 RequestListColumnStatus, 37 RequestListColumnTime, 38 RequestListColumnTransferredSize, 39 RequestListColumnType, 40 RequestListColumnUrl, 41 RequestListColumnWaterfall, 42 RequestListColumnPriority 43 */ 44 loader.lazyGetter(this, "RequestListColumnInitiator", function () { 45 return createFactory( 46 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnInitiator.js") 47 ); 48 }); 49 loader.lazyGetter(this, "RequestListColumnContentSize", function () { 50 return createFactory( 51 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnContentSize.js") 52 ); 53 }); 54 loader.lazyGetter(this, "RequestListColumnCookies", function () { 55 return createFactory( 56 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnCookies.js") 57 ); 58 }); 59 loader.lazyGetter(this, "RequestListColumnDomain", function () { 60 return createFactory( 61 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnDomain.js") 62 ); 63 }); 64 loader.lazyGetter(this, "RequestListColumnFile", function () { 65 return createFactory( 66 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnFile.js") 67 ); 68 }); 69 loader.lazyGetter(this, "RequestListColumnPath", function () { 70 return createFactory( 71 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnPath.js") 72 ); 73 }); 74 loader.lazyGetter(this, "RequestListColumnUrl", function () { 75 return createFactory( 76 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnUrl.js") 77 ); 78 }); 79 loader.lazyGetter(this, "RequestListColumnMethod", function () { 80 return createFactory( 81 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnMethod.js") 82 ); 83 }); 84 loader.lazyGetter(this, "RequestListColumnOverride", function () { 85 return createFactory( 86 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnOverride.js") 87 ); 88 }); 89 loader.lazyGetter(this, "RequestListColumnProtocol", function () { 90 return createFactory( 91 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnProtocol.js") 92 ); 93 }); 94 loader.lazyGetter(this, "RequestListColumnRemoteIP", function () { 95 return createFactory( 96 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnRemoteIP.js") 97 ); 98 }); 99 loader.lazyGetter(this, "RequestListColumnResponseHeader", function () { 100 return createFactory( 101 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnResponseHeader.js") 102 ); 103 }); 104 loader.lazyGetter(this, "RequestListColumnTime", function () { 105 return createFactory( 106 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnTime.js") 107 ); 108 }); 109 loader.lazyGetter(this, "RequestListColumnScheme", function () { 110 return createFactory( 111 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnScheme.js") 112 ); 113 }); 114 loader.lazyGetter(this, "RequestListColumnSetCookies", function () { 115 return createFactory( 116 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnSetCookies.js") 117 ); 118 }); 119 loader.lazyGetter(this, "RequestListColumnStatus", function () { 120 return createFactory( 121 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnStatus.js") 122 ); 123 }); 124 loader.lazyGetter(this, "RequestListColumnTransferredSize", function () { 125 return createFactory( 126 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnTransferredSize.js") 127 ); 128 }); 129 loader.lazyGetter(this, "RequestListColumnType", function () { 130 return createFactory( 131 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnType.js") 132 ); 133 }); 134 loader.lazyGetter(this, "RequestListColumnWaterfall", function () { 135 return createFactory( 136 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnWaterfall.js") 137 ); 138 }); 139 loader.lazyGetter(this, "RequestListColumnPriority", function () { 140 return createFactory( 141 require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnPriority.js") 142 ); 143 }); 144 145 /** 146 * Used by shouldComponentUpdate: compare two items, and compare only properties 147 * relevant for rendering the RequestListItem. Other properties (like request and 148 * response headers, cookies, bodies) are ignored. These are very useful for the 149 * network details, but not here. 150 */ 151 const UPDATED_REQ_ITEM_PROPS = [ 152 "mimeType", 153 "eventTimings", 154 "earlyHintsStatus", 155 "securityState", 156 "status", 157 "statusText", 158 "fromCache", 159 "isRacing", 160 "fromServiceWorker", 161 "method", 162 "url", 163 "remoteAddress", 164 "cause", 165 "contentSize", 166 "transferredSize", 167 "startedMs", 168 "totalTime", 169 "requestCookies", 170 "requestHeaders", 171 "responseCookies", 172 "responseHeaders", 173 "waitingTime", 174 "isEventStream", 175 "priority", 176 "blockedReason", 177 "extension", 178 ]; 179 180 const UPDATED_REQ_PROPS = [ 181 "firstRequestStartedMs", 182 "index", 183 "networkDetailsOpen", 184 "isSelected", 185 "isVisible", 186 "requestFilterTypes", 187 "waterfallScale", 188 ]; 189 190 /** 191 * Used by render: renders the given ColumnComponent if the flag for this column 192 * is set in the columns prop. The list of props are used to determine which of 193 * RequestListItem's need to be passed to the ColumnComponent. Any objects contained 194 * in that list are passed as props verbatim. 195 */ 196 const COLUMN_COMPONENTS = [ 197 { column: "override", ColumnComponent: RequestListColumnOverride }, 198 { column: "status", ColumnComponent: RequestListColumnStatus }, 199 { column: "method", ColumnComponent: RequestListColumnMethod }, 200 { 201 column: "domain", 202 ColumnComponent: RequestListColumnDomain, 203 props: ["onSecurityIconMouseDown"], 204 }, 205 { 206 column: "file", 207 ColumnComponent: RequestListColumnFile, 208 props: ["onWaterfallMouseDown", "slowLimit"], 209 }, 210 { 211 column: "path", 212 ColumnComponent: RequestListColumnPath, 213 props: ["onWaterfallMouseDown"], 214 }, 215 { 216 column: "url", 217 ColumnComponent: RequestListColumnUrl, 218 props: ["onSecurityIconMouseDown"], 219 }, 220 { column: "protocol", ColumnComponent: RequestListColumnProtocol }, 221 { column: "scheme", ColumnComponent: RequestListColumnScheme }, 222 { column: "remoteip", ColumnComponent: RequestListColumnRemoteIP }, 223 { 224 column: "initiator", 225 ColumnComponent: RequestListColumnInitiator, 226 props: ["onInitiatorBadgeMouseDown"], 227 }, 228 { column: "type", ColumnComponent: RequestListColumnType }, 229 { 230 column: "cookies", 231 ColumnComponent: RequestListColumnCookies, 232 props: ["connector"], 233 }, 234 { 235 column: "setCookies", 236 ColumnComponent: RequestListColumnSetCookies, 237 props: ["connector"], 238 }, 239 { column: "transferred", ColumnComponent: RequestListColumnTransferredSize }, 240 { column: "contentSize", ColumnComponent: RequestListColumnContentSize }, 241 { column: "priority", ColumnComponent: RequestListColumnPriority }, 242 { 243 column: "startTime", 244 ColumnComponent: RequestListColumnTime, 245 props: ["connector", "firstRequestStartedMs", { type: "start" }], 246 }, 247 { 248 column: "endTime", 249 ColumnComponent: RequestListColumnTime, 250 props: ["connector", "firstRequestStartedMs", { type: "end" }], 251 }, 252 { 253 column: "responseTime", 254 ColumnComponent: RequestListColumnTime, 255 props: ["connector", "firstRequestStartedMs", { type: "response" }], 256 }, 257 { 258 column: "duration", 259 ColumnComponent: RequestListColumnTime, 260 props: ["connector", "firstRequestStartedMs", { type: "duration" }], 261 }, 262 { 263 column: "latency", 264 ColumnComponent: RequestListColumnTime, 265 props: ["connector", "firstRequestStartedMs", { type: "latency" }], 266 }, 267 ]; 268 269 /** 270 * Render one row in the request list. 271 */ 272 class RequestListItem extends Component { 273 static get propTypes() { 274 return { 275 blocked: PropTypes.bool, 276 columns: PropTypes.object.isRequired, 277 connector: PropTypes.object.isRequired, 278 firstRequestStartedMs: PropTypes.number.isRequired, 279 fromCache: PropTypes.bool, 280 item: PropTypes.object.isRequired, 281 index: PropTypes.number.isRequired, 282 intersectionObserver: PropTypes.object, 283 isSelected: PropTypes.bool.isRequired, 284 isVisible: PropTypes.bool.isRequired, 285 networkActionOpen: PropTypes.bool, 286 networkDetailsOpen: PropTypes.bool, 287 onContextMenu: PropTypes.func.isRequired, 288 onDoubleClick: PropTypes.func.isRequired, 289 onDragStart: PropTypes.func.isRequired, 290 onFocusedNodeChange: PropTypes.func, 291 onInitiatorBadgeMouseDown: PropTypes.func.isRequired, 292 onMouseDown: PropTypes.func.isRequired, 293 onSecurityIconMouseDown: PropTypes.func.isRequired, 294 onWaterfallMouseDown: PropTypes.func.isRequired, 295 overriddenUrl: PropTypes.string, 296 requestFilterTypes: PropTypes.object.isRequired, 297 selectedActionBarTabId: PropTypes.string, 298 waterfallScale: PropTypes.number, 299 }; 300 } 301 302 componentDidMount() { 303 if (this.props.isSelected) { 304 this.refs.listItem.focus(); 305 } 306 if (this.props.intersectionObserver) { 307 this.props.intersectionObserver.observe(this.refs.listItem); 308 } 309 310 const { connector, item, requestFilterTypes } = this.props; 311 // Filtering XHR & WS require to lazily fetch requestHeaders & responseHeaders 312 if (requestFilterTypes.xhr || requestFilterTypes.ws) { 313 fetchNetworkUpdatePacket(connector.requestData, item, [ 314 "requestHeaders", 315 "responseHeaders", 316 ]); 317 } 318 } 319 320 // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 321 UNSAFE_componentWillReceiveProps(nextProps) { 322 const { connector, item, requestFilterTypes } = nextProps; 323 // Filtering XHR & WS require to lazily fetch requestHeaders & responseHeaders 324 if (requestFilterTypes.xhr || requestFilterTypes.ws) { 325 fetchNetworkUpdatePacket(connector.requestData, item, [ 326 "requestHeaders", 327 "responseHeaders", 328 ]); 329 } 330 } 331 332 shouldComponentUpdate(nextProps) { 333 return ( 334 !propertiesEqual( 335 UPDATED_REQ_ITEM_PROPS, 336 this.props.item, 337 nextProps.item 338 ) || 339 !propertiesEqual(UPDATED_REQ_PROPS, this.props, nextProps) || 340 this.props.columns !== nextProps.columns || 341 nextProps.overriddenUrl !== this.props.overriddenUrl 342 ); 343 } 344 345 componentDidUpdate(prevProps) { 346 if (!prevProps.isSelected && this.props.isSelected) { 347 this.refs.listItem.focus(); 348 if (this.props.onFocusedNodeChange) { 349 this.props.onFocusedNodeChange(); 350 } 351 } 352 } 353 354 componentWillUnmount() { 355 if (this.props.intersectionObserver) { 356 this.props.intersectionObserver.unobserve(this.refs.listItem); 357 } 358 } 359 360 render() { 361 const { 362 blocked, 363 connector, 364 columns, 365 item, 366 index, 367 isSelected, 368 isVisible, 369 firstRequestStartedMs, 370 fromCache, 371 networkActionOpen, 372 onDoubleClick, 373 onDragStart, 374 onContextMenu, 375 onMouseDown, 376 onWaterfallMouseDown, 377 selectedActionBarTabId, 378 waterfallScale, 379 } = this.props; 380 381 const classList = ["request-list-item", index % 2 ? "odd" : "even"]; 382 isSelected && classList.push("selected"); 383 fromCache && classList.push("fromCache"); 384 blocked && classList.push("blocked"); 385 386 return dom.tr( 387 { 388 ref: "listItem", 389 className: classList.join(" "), 390 "data-id": item.id, 391 draggable: 392 !blocked && 393 networkActionOpen && 394 selectedActionBarTabId === PANELS.BLOCKING, 395 tabIndex: 0, 396 onContextMenu, 397 onMouseDown, 398 onDoubleClick, 399 onDragStart, 400 }, 401 ...COLUMN_COMPONENTS.filter(({ column }) => columns[column]).map( 402 ({ column, ColumnComponent, props: columnProps }) => { 403 return ColumnComponent({ 404 key: column, 405 item, 406 ...(columnProps || []).reduce((acc, keyOrObject) => { 407 if (typeof keyOrObject == "string") { 408 acc[keyOrObject] = this.props[keyOrObject]; 409 } else { 410 Object.assign(acc, keyOrObject); 411 } 412 return acc; 413 }, {}), 414 }); 415 } 416 ), 417 ...RESPONSE_HEADERS.filter(header => columns[header]).map(header => 418 RequestListColumnResponseHeader({ 419 connector, 420 item, 421 header, 422 }) 423 ), 424 // The last column is Waterfall (aka Timeline) 425 columns.waterfall && 426 RequestListColumnWaterfall({ 427 connector, 428 firstRequestStartedMs, 429 item, 430 onWaterfallMouseDown, 431 isVisible, 432 waterfallScale, 433 }) 434 ); 435 } 436 } 437 438 module.exports = RequestListItem;