RequestListColumnWaterfall.js (5395B)
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 } = require("resource://devtools/client/shared/vendor/react.mjs"); 10 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 12 13 const { 14 L10N, 15 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); 16 const { 17 fetchNetworkUpdatePacket, 18 propertiesEqual, 19 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 20 21 // List of properties of the timing info we want to create boxes for 22 const { 23 TIMING_KEYS, 24 } = require("resource://devtools/client/netmonitor/src/constants.js"); 25 26 const { div } = dom; 27 28 const UPDATED_WATERFALL_ITEM_PROPS = ["eventTimings", "totalTime"]; 29 const UPDATED_WATERFALL_PROPS = [ 30 "item", 31 "firstRequestStartedMs", 32 "waterfallScale", 33 "isVisible", 34 ]; 35 36 module.exports = class RequestListColumnWaterfall extends Component { 37 static get propTypes() { 38 return { 39 connector: PropTypes.object.isRequired, 40 firstRequestStartedMs: PropTypes.number.isRequired, 41 item: PropTypes.object.isRequired, 42 onWaterfallMouseDown: PropTypes.func.isRequired, 43 waterfallScale: PropTypes.number, 44 isVisible: PropTypes.bool.isRequired, 45 }; 46 } 47 48 constructor() { 49 super(); 50 this.handleMouseOver = this.handleMouseOver.bind(this); 51 } 52 53 componentDidMount() { 54 const { connector, item } = this.props; 55 fetchNetworkUpdatePacket(connector.requestData, item, ["eventTimings"]); 56 } 57 58 // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 59 UNSAFE_componentWillReceiveProps(nextProps) { 60 if (nextProps.isVisible && typeof nextProps.item.totalTime === "number") { 61 const { connector, item } = nextProps; 62 fetchNetworkUpdatePacket(connector.requestData, item, ["eventTimings"]); 63 } 64 } 65 66 shouldComponentUpdate(nextProps) { 67 return ( 68 nextProps.isVisible && 69 (!propertiesEqual(UPDATED_WATERFALL_PROPS, this.props, nextProps) || 70 !propertiesEqual( 71 UPDATED_WATERFALL_ITEM_PROPS, 72 this.props.item, 73 nextProps.item 74 )) 75 ); 76 } 77 78 handleMouseOver({ target }) { 79 if (!target.title) { 80 target.title = this.timingTooltip(); 81 } 82 } 83 84 timingTooltip() { 85 const { eventTimings, totalTime } = this.props.item; 86 const tooltip = []; 87 88 if (eventTimings) { 89 for (const key of TIMING_KEYS) { 90 const width = eventTimings.timings[key]; 91 92 if (width > 0) { 93 tooltip.push( 94 L10N.getFormatStr("netmonitor.waterfall.tooltip." + key, width) 95 ); 96 } 97 } 98 } 99 100 if (typeof totalTime === "number") { 101 tooltip.push( 102 L10N.getFormatStr("netmonitor.waterfall.tooltip.total", totalTime) 103 ); 104 } 105 106 const lf = new Intl.ListFormat(undefined, { type: "unit" }); 107 return lf.format(tooltip); 108 } 109 110 timingBoxes() { 111 const { 112 waterfallScale, 113 item: { eventTimings, totalTime }, 114 } = this.props; 115 const boxes = []; 116 117 // Physical pixel as minimum size 118 const minPixel = 1 / window.devicePixelRatio; 119 120 if (typeof totalTime === "number") { 121 if (eventTimings) { 122 // Add a set of boxes representing timing information. 123 for (const key of TIMING_KEYS) { 124 if (eventTimings.timings[key] > 0) { 125 boxes.push( 126 div({ 127 key, 128 className: `requests-list-timings-box ${key}`, 129 style: { 130 width: Math.max( 131 eventTimings.timings[key] * waterfallScale, 132 minPixel 133 ), 134 }, 135 }) 136 ); 137 } 138 } 139 } 140 // Minimal box to at least show start and total time 141 if (!boxes.length) { 142 boxes.push( 143 div({ 144 className: "requests-list-timings-box filler", 145 key: "filler", 146 style: { width: Math.max(totalTime * waterfallScale, minPixel) }, 147 }) 148 ); 149 } 150 151 const title = L10N.getFormatStr("networkMenu.totalMS2", totalTime); 152 boxes.push( 153 div( 154 { 155 key: "total", 156 className: "requests-list-timings-total", 157 title, 158 }, 159 title 160 ) 161 ); 162 } else { 163 // Pending requests are marked for start time 164 boxes.push( 165 div({ 166 className: "requests-list-timings-box filler", 167 key: "pending", 168 style: { width: minPixel }, 169 }) 170 ); 171 } 172 173 return boxes; 174 } 175 176 render() { 177 const { 178 firstRequestStartedMs, 179 item: { startedMs }, 180 waterfallScale, 181 onWaterfallMouseDown, 182 } = this.props; 183 184 return dom.td( 185 { 186 className: "requests-list-column requests-list-waterfall", 187 onMouseOver: this.handleMouseOver, 188 }, 189 div( 190 { 191 className: "requests-list-timings", 192 style: { 193 paddingInlineStart: `${ 194 (startedMs - firstRequestStartedMs) * waterfallScale 195 }px`, 196 }, 197 onMouseDown: onWaterfallMouseDown, 198 }, 199 this.timingBoxes() 200 ) 201 ); 202 } 203 };