/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { getUrlBaseName, } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); /** * Tests if request initiator is reported correctly. */ const INITIATOR_FILE_NAME = "html_cause-test-page.html"; const INITIATOR_URL = HTTPS_EXAMPLE_URL + INITIATOR_FILE_NAME; const EXPECTED_REQUESTS = [ { method: "GET", url: INITIATOR_URL, causeType: "document", causeUri: null, stack: false, }, { method: "GET", url: HTTPS_EXAMPLE_URL + "stylesheet_request", causeType: "stylesheet", causeUri: INITIATOR_URL, stack: false, }, { method: "GET", url: HTTPS_EXAMPLE_URL + "img_request", causeType: "img", causeUri: INITIATOR_URL, stack: false, }, { method: "GET", url: HTTPS_EXAMPLE_URL + "img_srcset_request", causeType: "imageset", causeUri: INITIATOR_URL, stack: false, }, { method: "GET", url: HTTPS_EXAMPLE_URL + "xhr_request", causeType: "xhr", causeUri: INITIATOR_URL, stack: [{ fn: "performXhrRequestCallback", file: INITIATOR_URL, line: 32 }], }, { method: "GET", url: HTTPS_EXAMPLE_URL + "fetch_request", causeType: "fetch", causeUri: INITIATOR_URL, stack: [{ fn: "performFetchRequest", file: INITIATOR_URL, line: 37 }], }, { method: "GET", url: HTTPS_EXAMPLE_URL + "promise_fetch_request", causeType: "fetch", causeUri: INITIATOR_URL, stack: [ { fn: "performPromiseFetchRequestCallback", file: INITIATOR_URL, line: 43, }, { fn: "performPromiseFetchRequest", file: INITIATOR_URL, line: 42, asyncCause: "promise callback", }, ], }, { method: "GET", url: HTTPS_EXAMPLE_URL + "timeout_fetch_request", causeType: "fetch", causeUri: INITIATOR_URL, stack: [ { fn: "performTimeoutFetchRequestCallback2", file: INITIATOR_URL, line: 50, }, { fn: "performTimeoutFetchRequestCallback1", file: INITIATOR_URL, line: 49, asyncCause: "setTimeout handler", }, ], }, { method: "GET", url: HTTPS_EXAMPLE_URL + "favicon_request", causeType: "img", causeUri: INITIATOR_URL, // the favicon request is triggered in FaviconLoader.jsm module, it should // NOT be shown in the stack (bug 1280266). For now we intentionally // specify the file and the line number to be properly sorted. // NOTE: The line number can be an arbitrary number greater than 0. stack: [ { file: "resource:///modules/FaviconLoader.jsm", line: Number.MAX_SAFE_INTEGER, }, ], }, { method: "GET", url: HTTPS_EXAMPLE_URL + "lazy_img_request", causeType: "lazy-img", causeUri: INITIATOR_URL, stack: false, }, { method: "GET", url: HTTPS_EXAMPLE_URL + "lazy_img_srcset_request", causeType: "lazy-imageset", causeUri: INITIATOR_URL, stack: false, }, { method: "POST", url: HTTPS_EXAMPLE_URL + "beacon_request", causeType: "beacon", causeUri: INITIATOR_URL, stack: [{ fn: "performBeaconRequest", file: INITIATOR_URL, line: 82 }], }, ]; add_task(async function () { // the initNetMonitor function clears the network request list after the // page is loaded. That's why we first load a bogus page from SIMPLE_URL, // and only then load the real thing from INITIATOR_URL - we want to catch // all the requests the page is making, not only the XHRs. // We can't use about:blank here, because initNetMonitor checks that the // page has actually made at least one request. const { tab, monitor } = await initNetMonitor(SIMPLE_URL, { requestCount: 1, }); const { document, store, windowRequire } = monitor.panelWin; const Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); const { getSortedRequests } = windowRequire( "devtools/client/netmonitor/src/selectors/index" ); store.dispatch(Actions.batchEnable(false)); const wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); BrowserTestUtils.loadURIString(tab.linkedBrowser, INITIATOR_URL); registerFaviconNotifier(tab.linkedBrowser); await wait; // For all expected requests for (const [index, { stack }] of EXPECTED_REQUESTS.entries()) { if (!stack) { continue; } EventUtils.sendMouseEvent( { type: "mousedown" }, document.querySelectorAll( ".request-list-item .requests-list-initiator-lastframe" )[index] ); // Clicking on the initiator column should open the Stack Trace panel const onStackTraceRendered = waitUntil(() => document.querySelector("#stack-trace-panel .stack-trace .frame-link") ); await onStackTraceRendered; } is( store.getState().requests.requests.length, EXPECTED_REQUESTS.length, "All the page events should be recorded." ); validateRequests(EXPECTED_REQUESTS, monitor); // Sort the requests by initiator and check the order EventUtils.sendMouseEvent( { type: "click" }, document.querySelector("#requests-list-initiator-button") ); const expectedOrder = EXPECTED_REQUESTS.sort(initiatorSortPredicate).map( r => { let isChromeFrames = false; const lastFrameExists = !!r.stack; let initiator = ""; let lineNumber = ""; if (lastFrameExists) { const { file, line: _lineNumber } = r.stack[0]; initiator = getUrlBaseName(file); lineNumber = ":" + _lineNumber; isChromeFrames = file.startsWith("resource:///"); } const causeStr = lastFrameExists ? " (" + r.causeType + ")" : r.causeType; return { initiatorStr: initiator + lineNumber + causeStr, isChromeFrames, }; } ); expectedOrder.forEach((expectedInitiator, i) => { const request = getSortedRequests(store.getState())[i]; let initiator; // In cases of chrome frames, we shouldn't have stack. if ( request.cause.stacktraceAvailable && !expectedInitiator.isChromeFrames ) { const { filename, lineNumber } = request.cause.lastFrame; initiator = getUrlBaseName(filename) + ":" + lineNumber + " (" + request.cause.type + ")"; } else { initiator = request.cause.type; } if (expectedInitiator.isChromeFrames) { todo_is( initiator, expectedInitiator.initiatorStr, `The request #${i} has the expected initiator after sorting` ); } else { is( initiator, expectedInitiator.initiatorStr, `The request #${i} has the expected initiator after sorting` ); } }); await teardown(monitor); }); // derived from devtools/client/netmonitor/src/utils/sort-predicates.js function initiatorSortPredicate(first, second) { const firstLastFrame = first.stack ? first.stack[0] : null; const secondLastFrame = second.stack ? second.stack[0] : null; let firstInitiator = ""; let firstInitiatorLineNumber = 0; if (firstLastFrame) { firstInitiator = getUrlBaseName(firstLastFrame.file); firstInitiatorLineNumber = firstLastFrame.line; } let secondInitiator = ""; let secondInitiatorLineNumber = 0; if (secondLastFrame) { secondInitiator = getUrlBaseName(secondLastFrame.file); secondInitiatorLineNumber = secondLastFrame.line; } let result; // if both initiators don't have a stack trace, compare their causes if (!firstInitiator && !secondInitiator) { result = compareValues(first.causeType, second.causeType); } else if (!firstInitiator || !secondInitiator) { // if one initiator doesn't have a stack trace but the other does, former should precede the latter result = compareValues(firstInitiatorLineNumber, secondInitiatorLineNumber); } else { result = compareValues(firstInitiator, secondInitiator); if (result === 0) { result = compareValues( firstInitiatorLineNumber, secondInitiatorLineNumber ); } } return result; }