sort-predicates.js (9563B)
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 getAbbreviatedMimeType, 9 getEndTime, 10 getResponseTime, 11 getResponseHeader, 12 getStartTime, 13 ipToLong, 14 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 15 const { 16 RESPONSE_HEADERS, 17 } = require("resource://devtools/client/netmonitor/src/constants.js"); 18 const { 19 getUrlBaseName, 20 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 21 22 /** 23 * Predicates used when sorting items. 24 * 25 * @param object first 26 * The first item used in the comparison. 27 * @param object second 28 * The second item used in the comparison. 29 * @return number 30 * <0 to sort first to a lower index than second 31 * =0 to leave first and second unchanged with respect to each other 32 * >0 to sort second to a lower index than first 33 */ 34 35 function compareValues(first, second) { 36 if (first === second) { 37 return 0; 38 } 39 if (first === undefined) { 40 return 1; 41 } 42 if (second === undefined) { 43 return -1; 44 } 45 return first > second ? 1 : -1; 46 } 47 48 function waterfall(first, second) { 49 const result = compareValues(first.startedMs, second.startedMs); 50 return result || compareValues(first.id, second.id); 51 } 52 53 function priority(first, second) { 54 const result = compareValues(first.priority, second.priority); 55 return result || waterfall(first, second); 56 } 57 58 function status(first, second) { 59 const result = compareValues(getStatusValue(first), getStatusValue(second)); 60 return result || waterfall(first, second); 61 } 62 63 function method(first, second) { 64 const result = compareValues(first.method, second.method); 65 return result || waterfall(first, second); 66 } 67 68 function file(first, second) { 69 const firstUrl = first.urlDetails.baseNameWithQuery.toLowerCase(); 70 const secondUrl = second.urlDetails.baseNameWithQuery.toLowerCase(); 71 const result = compareValues(firstUrl, secondUrl); 72 return result || waterfall(first, second); 73 } 74 75 function path(first, second) { 76 const firstUrl = first.urlDetails.path.toLowerCase(); 77 const secondUrl = second.urlDetails.path.toLowerCase(); 78 const result = compareValues(firstUrl, secondUrl); 79 return result || waterfall(first, second); 80 } 81 82 function url(first, second) { 83 const firstUrl = first.url.toLowerCase(); 84 const secondUrl = second.url.toLowerCase(); 85 const result = compareValues(firstUrl, secondUrl); 86 return result || waterfall(first, second); 87 } 88 89 function protocol(first, second) { 90 const result = compareValues(first.httpVersion, second.httpVersion); 91 return result || waterfall(first, second); 92 } 93 94 function scheme(first, second) { 95 const result = compareValues( 96 first.urlDetails.scheme, 97 second.urlDetails.scheme 98 ); 99 return result || waterfall(first, second); 100 } 101 102 function startTime(first, second) { 103 const result = compareValues(getStartTime(first), getStartTime(second)); 104 return result || waterfall(first, second); 105 } 106 107 function endTime(first, second) { 108 const result = compareValues(getEndTime(first), getEndTime(second)); 109 return result || waterfall(first, second); 110 } 111 112 function responseTime(first, second) { 113 const result = compareValues(getResponseTime(first), getResponseTime(second)); 114 return result || waterfall(first, second); 115 } 116 117 function duration(first, second) { 118 const result = compareValues(first.totalTime, second.totalTime); 119 return result || waterfall(first, second); 120 } 121 122 function latency(first, second) { 123 const { eventTimings: firstEventTimings = { timings: {} } } = first; 124 const { eventTimings: secondEventTimings = { timings: {} } } = second; 125 const result = compareValues( 126 firstEventTimings.timings.wait, 127 secondEventTimings.timings.wait 128 ); 129 return result || waterfall(first, second); 130 } 131 132 function compareHeader(header, first, second) { 133 const firstValue = getResponseHeader(first, header) || ""; 134 const secondValue = getResponseHeader(second, header) || ""; 135 136 let result; 137 138 switch (header) { 139 case "Content-Length": { 140 result = compareValues( 141 parseInt(firstValue, 10) || 0, 142 parseInt(secondValue, 10) || 0 143 ); 144 break; 145 } 146 case "Last-Modified": { 147 result = compareValues( 148 new Date(firstValue).valueOf() || -1, 149 new Date(secondValue).valueOf() || -1 150 ); 151 break; 152 } 153 default: { 154 result = compareValues(firstValue, secondValue); 155 break; 156 } 157 } 158 159 return result || waterfall(first, second); 160 } 161 162 const responseHeaders = RESPONSE_HEADERS.reduce( 163 (acc, header) => 164 Object.assign(acc, { 165 [header]: (first, second) => compareHeader(header, first, second), 166 }), 167 {} 168 ); 169 170 function domain(first, second) { 171 const firstDomain = first.urlDetails.host.toLowerCase(); 172 const secondDomain = second.urlDetails.host.toLowerCase(); 173 const result = compareValues(firstDomain, secondDomain); 174 return result || waterfall(first, second); 175 } 176 177 function remoteip(first, second) { 178 const firstIP = ipToLong(first.remoteAddress); 179 const secondIP = ipToLong(second.remoteAddress); 180 const result = compareValues(firstIP, secondIP); 181 return result || waterfall(first, second); 182 } 183 184 function cause(first, second) { 185 const firstCause = first.cause.type; 186 const secondCause = second.cause.type; 187 const result = compareValues(firstCause, secondCause); 188 return result || waterfall(first, second); 189 } 190 191 function initiator(first, second) { 192 const firstCause = first.cause.type; 193 const secondCause = second.cause.type; 194 195 let firstInitiator = ""; 196 let firstInitiatorLineNumber = 0; 197 198 if (first.cause.lastFrame) { 199 firstInitiator = getUrlBaseName(first.cause.lastFrame.filename); 200 firstInitiatorLineNumber = first.cause.lastFrame.lineNumber; 201 } 202 203 let secondInitiator = ""; 204 let secondInitiatorLineNumber = 0; 205 206 if (second.cause.lastFrame) { 207 secondInitiator = getUrlBaseName(second.cause.lastFrame.filename); 208 secondInitiatorLineNumber = second.cause.lastFrame.lineNumber; 209 } 210 211 let result; 212 // if both initiators don't have a stack trace, compare their causes 213 if (!firstInitiator && !secondInitiator) { 214 result = compareValues(firstCause, secondCause); 215 } else if (!firstInitiator || !secondInitiator) { 216 // if one initiator doesn't have a stack trace but the other does, former should precede the latter 217 result = compareValues(firstInitiatorLineNumber, secondInitiatorLineNumber); 218 } else { 219 result = compareValues(firstInitiator, secondInitiator); 220 if (result === 0) { 221 result = compareValues( 222 firstInitiatorLineNumber, 223 secondInitiatorLineNumber 224 ); 225 } 226 } 227 228 return result || waterfall(first, second); 229 } 230 231 function setCookies(first, second) { 232 let { responseCookies: firstResponseCookies = { cookies: [] } } = first; 233 let { responseCookies: secondResponseCookies = { cookies: [] } } = second; 234 firstResponseCookies = firstResponseCookies.cookies || firstResponseCookies; 235 secondResponseCookies = 236 secondResponseCookies.cookies || secondResponseCookies; 237 const result = compareValues( 238 firstResponseCookies.length, 239 secondResponseCookies.length 240 ); 241 return result || waterfall(first, second); 242 } 243 244 function cookies(first, second) { 245 let { requestCookies: firstRequestCookies = { cookies: [] } } = first; 246 let { requestCookies: secondRequestCookies = { cookies: [] } } = second; 247 firstRequestCookies = firstRequestCookies.cookies || firstRequestCookies; 248 secondRequestCookies = secondRequestCookies.cookies || secondRequestCookies; 249 const result = compareValues( 250 firstRequestCookies.length, 251 secondRequestCookies.length 252 ); 253 return result || waterfall(first, second); 254 } 255 256 function type(first, second) { 257 const firstType = getAbbreviatedMimeType(first.mimeType); 258 const secondType = getAbbreviatedMimeType(second.mimeType); 259 const result = compareValues(firstType, secondType); 260 return result || waterfall(first, second); 261 } 262 263 function getStatusValue(item) { 264 let value; 265 if (item.blockedReason) { 266 value = typeof item.blockedReason == "number" ? -item.blockedReason : -1000; 267 } else if (item.status == null) { 268 value = -2; 269 } else { 270 value = item.status; 271 } 272 return value; 273 } 274 275 function getTransferedSizeValue(item) { 276 let value; 277 if (item.blockedReason) { 278 // Also properly group/sort various blocked reasons. 279 value = typeof item.blockedReason == "number" ? -item.blockedReason : -1000; 280 } else if (item.fromCache || item.status === "304") { 281 value = -2; 282 } else if (item.fromServiceWorker) { 283 value = -3; 284 } else if (typeof item.transferredSize == "number") { 285 value = item.transferredSize; 286 if (item.isRacing && typeof item.isRacing == "boolean") { 287 value = -4; 288 } 289 } else if (item.transferredSize === null) { 290 value = -5; 291 } 292 return value; 293 } 294 295 function transferred(first, second) { 296 const result = compareValues( 297 getTransferedSizeValue(first), 298 getTransferedSizeValue(second) 299 ); 300 return result || waterfall(first, second); 301 } 302 303 function contentSize(first, second) { 304 const result = compareValues(first.contentSize, second.contentSize); 305 return result || waterfall(first, second); 306 } 307 308 const sorters = { 309 status, 310 method, 311 domain, 312 file, 313 path, 314 protocol, 315 scheme, 316 cookies, 317 setCookies, 318 remoteip, 319 cause, 320 initiator, 321 type, 322 transferred, 323 contentSize, 324 startTime, 325 endTime, 326 responseTime, 327 duration, 328 latency, 329 waterfall, 330 url, 331 priority, 332 }; 333 exports.Sorters = Object.assign(sorters, responseHeaders);