browser_net_initiator.js (7958B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 const { 6 getUrlBaseName, 7 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 8 /** 9 * Tests if request initiator is reported correctly. 10 */ 11 12 const INITIATOR_FILE_NAME = "html_cause-test-page.html"; 13 const INITIATOR_URL = HTTPS_EXAMPLE_URL + INITIATOR_FILE_NAME; 14 15 const EXPECTED_REQUESTS = [ 16 { 17 method: "GET", 18 url: INITIATOR_URL, 19 causeType: "document", 20 causeUri: null, 21 stack: false, 22 }, 23 { 24 method: "GET", 25 url: HTTPS_EXAMPLE_URL + "stylesheet_request", 26 causeType: "stylesheet", 27 causeUri: INITIATOR_URL, 28 stack: false, 29 }, 30 { 31 method: "GET", 32 url: HTTPS_EXAMPLE_URL + "img_request", 33 causeType: "img", 34 causeUri: INITIATOR_URL, 35 stack: false, 36 }, 37 { 38 method: "GET", 39 url: HTTPS_EXAMPLE_URL + "img_srcset_request", 40 causeType: "imageset", 41 causeUri: INITIATOR_URL, 42 stack: false, 43 }, 44 { 45 method: "GET", 46 url: HTTPS_EXAMPLE_URL + "xhr_request", 47 causeType: "xhr", 48 causeUri: INITIATOR_URL, 49 stack: [{ fn: "performXhrRequestCallback", file: INITIATOR_URL, line: 32 }], 50 }, 51 { 52 method: "GET", 53 url: HTTPS_EXAMPLE_URL + "fetch_request", 54 causeType: "fetch", 55 causeUri: INITIATOR_URL, 56 stack: [{ fn: "performFetchRequest", file: INITIATOR_URL, line: 37 }], 57 }, 58 { 59 method: "GET", 60 url: HTTPS_EXAMPLE_URL + "promise_fetch_request", 61 causeType: "fetch", 62 causeUri: INITIATOR_URL, 63 stack: [ 64 { 65 fn: "performPromiseFetchRequestCallback", 66 file: INITIATOR_URL, 67 line: 43, 68 }, 69 { 70 fn: "performPromiseFetchRequest", 71 file: INITIATOR_URL, 72 line: 42, 73 asyncCause: "promise callback", 74 }, 75 ], 76 }, 77 { 78 method: "GET", 79 url: HTTPS_EXAMPLE_URL + "timeout_fetch_request", 80 causeType: "fetch", 81 causeUri: INITIATOR_URL, 82 stack: [ 83 { 84 fn: "performTimeoutFetchRequestCallback2", 85 file: INITIATOR_URL, 86 line: 50, 87 }, 88 { 89 fn: "performTimeoutFetchRequestCallback1", 90 file: INITIATOR_URL, 91 line: 49, 92 asyncCause: "setTimeout handler", 93 }, 94 ], 95 }, 96 { 97 method: "GET", 98 url: HTTPS_EXAMPLE_URL + "favicon_request", 99 causeType: "img", 100 causeUri: INITIATOR_URL, 101 // The favicon request is triggered in FaviconLoader.sys.mjs module, so it should 102 // NOT be shown in the stack (See bug 1280266). 103 stack: false, 104 }, 105 { 106 method: "GET", 107 url: HTTPS_EXAMPLE_URL + "lazy_img_request", 108 causeType: "lazy-img", 109 causeUri: INITIATOR_URL, 110 stack: false, 111 }, 112 { 113 method: "GET", 114 url: HTTPS_EXAMPLE_URL + "lazy_img_srcset_request", 115 causeType: "lazy-imageset", 116 causeUri: INITIATOR_URL, 117 stack: false, 118 }, 119 { 120 method: "POST", 121 url: HTTPS_EXAMPLE_URL + "beacon_request", 122 causeType: "beacon", 123 causeUri: INITIATOR_URL, 124 stack: [{ fn: "performBeaconRequest", file: INITIATOR_URL, line: 82 }], 125 }, 126 ]; 127 128 add_task(async function () { 129 // the initNetMonitor function clears the network request list after the 130 // page is loaded. That's why we first load a bogus page from SIMPLE_URL, 131 // and only then load the real thing from INITIATOR_URL - we want to catch 132 // all the requests the page is making, not only the XHRs. 133 // We can't use about:blank here, because initNetMonitor checks that the 134 // page has actually made at least one request. 135 const { tab, monitor } = await initNetMonitor(SIMPLE_URL, { 136 requestCount: 1, 137 }); 138 139 const { document, store, windowRequire } = monitor.panelWin; 140 const Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); 141 const { getSortedRequests } = windowRequire( 142 "devtools/client/netmonitor/src/selectors/index" 143 ); 144 145 store.dispatch(Actions.batchEnable(false)); 146 147 const wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); 148 BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, INITIATOR_URL); 149 150 registerFaviconNotifier(tab.linkedBrowser); 151 152 await wait; 153 154 // For all expected requests 155 for (const [index, { stack }] of EXPECTED_REQUESTS.entries()) { 156 if (!stack) { 157 continue; 158 } 159 160 EventUtils.sendMouseEvent( 161 { type: "mousedown" }, 162 document.querySelectorAll( 163 ".request-list-item .requests-list-initiator-lastframe" 164 )[index] 165 ); 166 167 // Clicking on the initiator column should open the Stack Trace panel 168 const onStackTraceRendered = waitUntil(() => 169 document.querySelector("#stack-trace-panel .stack-trace .frame-link") 170 ); 171 await onStackTraceRendered; 172 } 173 174 is( 175 store.getState().requests.requests.length, 176 EXPECTED_REQUESTS.length, 177 "All the page events should be recorded." 178 ); 179 180 await validateRequests(EXPECTED_REQUESTS, monitor); 181 182 // Sort the requests by initiator and check the order 183 EventUtils.sendMouseEvent( 184 { type: "click" }, 185 document.querySelector("#requests-list-initiator-button") 186 ); 187 188 const expectedOrder = EXPECTED_REQUESTS.sort(initiatorSortPredicate).map( 189 r => { 190 let isChromeFrames = false; 191 const lastFrameExists = !!r.stack; 192 let initiator = ""; 193 let lineNumber = ""; 194 if (lastFrameExists) { 195 const { file, line: _lineNumber } = r.stack[0]; 196 initiator = getUrlBaseName(file); 197 lineNumber = ":" + _lineNumber; 198 isChromeFrames = file.startsWith("resource:///"); 199 } 200 const causeStr = lastFrameExists ? " (" + r.causeType + ")" : r.causeType; 201 return { 202 initiatorStr: initiator + lineNumber + causeStr, 203 isChromeFrames, 204 }; 205 } 206 ); 207 208 expectedOrder.forEach((expectedInitiator, i) => { 209 const request = getSortedRequests(store.getState())[i]; 210 let initiator; 211 // In cases of chrome frames, we shouldn't have stack. 212 if ( 213 request.cause.stacktraceAvailable && 214 !expectedInitiator.isChromeFrames 215 ) { 216 const { filename, lineNumber } = request.cause.lastFrame; 217 initiator = 218 getUrlBaseName(filename) + 219 ":" + 220 lineNumber + 221 " (" + 222 request.cause.type + 223 ")"; 224 } else { 225 initiator = request.cause.type; 226 } 227 228 if (expectedInitiator.isChromeFrames) { 229 todo_is( 230 initiator, 231 expectedInitiator.initiatorStr, 232 `The request #${i} has the expected initiator after sorting` 233 ); 234 } else { 235 is( 236 initiator, 237 expectedInitiator.initiatorStr, 238 `The request #${i} has the expected initiator after sorting` 239 ); 240 } 241 }); 242 243 await teardown(monitor); 244 }); 245 246 // derived from devtools/client/netmonitor/src/utils/sort-predicates.js 247 function initiatorSortPredicate(first, second) { 248 const firstLastFrame = first.stack ? first.stack[0] : null; 249 const secondLastFrame = second.stack ? second.stack[0] : null; 250 251 let firstInitiator = ""; 252 let firstInitiatorLineNumber = 0; 253 254 if (firstLastFrame) { 255 firstInitiator = getUrlBaseName(firstLastFrame.file); 256 firstInitiatorLineNumber = firstLastFrame.line; 257 } 258 259 let secondInitiator = ""; 260 let secondInitiatorLineNumber = 0; 261 262 if (secondLastFrame) { 263 secondInitiator = getUrlBaseName(secondLastFrame.file); 264 secondInitiatorLineNumber = secondLastFrame.line; 265 } 266 267 let result; 268 // if both initiators don't have a stack trace, compare their causes 269 if (!firstInitiator && !secondInitiator) { 270 result = compareValues(first.causeType, second.causeType); 271 } else if (!firstInitiator || !secondInitiator) { 272 // if one initiator doesn't have a stack trace but the other does, former should precede the latter 273 result = compareValues(firstInitiatorLineNumber, secondInitiatorLineNumber); 274 } else { 275 result = compareValues(firstInitiator, secondInitiator); 276 if (result === 0) { 277 result = compareValues( 278 firstInitiatorLineNumber, 279 secondInitiatorLineNumber 280 ); 281 } 282 } 283 return result; 284 }