diff --git a/devtools/client/netmonitor/src/connector/firefox-connector.js b/devtools/client/netmonitor/src/connector/firefox-connector.js index 2146c7ac2538..f0b405f1da6b 100644 --- a/devtools/client/netmonitor/src/connector/firefox-connector.js +++ b/devtools/client/netmonitor/src/connector/firefox-connector.js @@ -325,12 +325,12 @@ class FirefoxConnector { } navigate() { - if (this.dataProvider.isPayloadQueueEmpty()) { + if (!this.dataProvider.hasPendingRequests()) { this.onReloaded(); return; } const listener = () => { - if (this.dataProvider && !this.dataProvider.isPayloadQueueEmpty()) { + if (this.dataProvider && this.dataProvider.hasPendingRequests()) { return; } if (this.owner) { diff --git a/devtools/client/netmonitor/src/connector/firefox-data-provider.js b/devtools/client/netmonitor/src/connector/firefox-data-provider.js index 1fecf1fdbbfb..2604bf68472f 100644 --- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js +++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js @@ -43,8 +43,8 @@ class FirefoxDataProvider { // Map of the stacktrace information keyed by the actor id's this.stackTraceRequestInfoByActorID = new Map(); - // Internal properties - this.payloadQueue = new Map(); + // For tracking unfinished requests + this.pendingRequests = new Set(); // Map[key string => Promise] used by `requestData` to prevent requesting the same // request data twice. @@ -84,32 +84,14 @@ class FirefoxDataProvider { * Add a new network request to application state. * * @param {string} id request id - * @param {object} data data payload will be added to application state + * @param {object} resource resource payload will be added to application state */ - async addRequest(id, data) { - const { startedDateTime, ...payload } = data; - - // Insert blocked reason in the payload queue as well, as we'll need it later - // when deciding if the request is complete. - this.pushRequestToQueue(id, { - blockedReason: payload.blockedReason, - }); + async addRequest(id, resource) { + // Add to the pending requests which helps when deciding if the request is complete. + this.pendingRequests.add(id); if (this.actionsEnabled && this.actions.addRequest) { - await this.actions.addRequest( - id, - { - ...payload, - // Convert the received date/time string to a unix timestamp. - startedMs: Date.parse(startedDateTime), - - // Compatibility code to support Firefox 58 and earlier that always - // send stack-trace immediately on networkEvent message. - // FF59+ supports fetching the traces lazily via requestData. - stacktrace: payload.cause.stacktrace, - }, - true - ); + await this.actions.addRequest(id, resource, true); } this.emit(EVENTS.REQUEST_ADDED, id); @@ -287,25 +269,10 @@ class FirefoxDataProvider { /** * Public API used by the Toolbox: Tells if there is still any pending request. * - * @return {boolean} returns true if the payload queue is empty + * @return {boolean} returns true if pending requests still exist in the queue. */ - isPayloadQueueEmpty() { - return this.payloadQueue.size === 0; - } - - /** - * Merge upcoming networkEventUpdate payload into existing one. - * - * @param {string} id request actor id - * @param {object} payload request data payload - */ - pushRequestToQueue(id, payload) { - let payloadFromQueue = this.payloadQueue.get(id); - if (!payloadFromQueue) { - payloadFromQueue = {}; - this.payloadQueue.set(id, payloadFromQueue); - } - Object.assign(payloadFromQueue, payload); + hasPendingRequests() { + return this.pendingRequests.size > 0; } /** @@ -359,22 +326,7 @@ class FirefoxDataProvider { * @param {object} resource The network event resource */ async onNetworkResourceAvailable(resource) { - const { - actor, - cause, - fromCache, - fromServiceWorker, - isXHR, - request: { method, url }, - response: { bodySize, ...responseProps }, - startedDateTime, - isThirdPartyTrackingResource, - referrerPolicy, - blockedReason, - blockingExtension, - resourceId, - stacktraceResourceId, - } = resource; + const { actor, stacktraceResourceId } = resource; // Check if a stacktrace resource exists for this network resource. // The stacktrace event is expected to happen before the network @@ -385,8 +337,10 @@ class FirefoxDataProvider { lastFrame, targetFront, } = this.stackTraces.get(stacktraceResourceId); - cause.stacktraceAvailable = stacktraceAvailable; - cause.lastFrame = lastFrame; + + resource.cause.stacktraceAvailable = stacktraceAvailable; + resource.cause.lastFrame = lastFrame; + this.stackTraces.delete(stacktraceResourceId); // We retrieve preliminary information about the stacktrace from the // NETWORK_EVENT_STACKTRACE resource via `this.stackTraces` Map, @@ -398,45 +352,7 @@ class FirefoxDataProvider { stacktraceResourceId, }); } - - // For resources from the resource watcher cache no updates are going to be fired - // as the resource already contains all the updated props. These need to be set so - // the UI knows the data is available on the backend. - const available = {}; - [ - "eventTimings", - "requestHeaders", - "requestPostData", - "responseHeaders", - "responseStart", - "responseContent", - "securityInfo", - "responseCache", - "responseCookies", - ].forEach(updateType => { - if (resource.updates.includes(updateType)) { - available[`${updateType}Available`] = true; - } - }); - - await this.addRequest(actor, { - cause, - fromCache, - fromServiceWorker, - isXHR, - method, - startedDateTime, - url, - isThirdPartyTrackingResource, - referrerPolicy, - blockedReason, - blockingExtension, - resourceId, - mimeType: resource?.content?.mimeType, - contentSize: bodySize, - ...responseProps, - ...available, - }); + await this.addRequest(actor, resource); this.emitForTests(TEST_EVENTS.NETWORK_EVENT, resource); } @@ -445,66 +361,23 @@ class FirefoxDataProvider { * * @param {object} resource The updated network event resource. */ - async onNetworkResourceUpdated(resource, update) { - switch (update.updateType) { - case "securityInfo": - this.pushRequestToQueue(resource.actor, { - securityState: resource.securityState, - isRacing: resource.isRacing, - }); - break; - case "responseStart": - this.pushRequestToQueue(resource.actor, { - httpVersion: resource.response.httpVersion, - remoteAddress: resource.response.remoteAddress, - remotePort: resource.response.remotePort, - status: resource.response.status, - statusText: resource.response.statusText, - headersSize: resource.response.headersSize, - waitingTime: resource.response.waitingTime, - }); - - // Identify the channel as SSE if mimeType is event-stream. - if (resource.response.content.mimeType?.includes("text/event-stream")) { - await this.setEventStreamFlag(resource.actor); - } - - this.emitForTests( - TEST_EVENTS.STARTED_RECEIVING_RESPONSE, - resource.actor - ); - break; - case "responseContent": - this.pushRequestToQueue(resource.actor, { - contentSize: resource.response.bodySize, - transferredSize: resource.response.transferredSize, - mimeType: resource.response.content.mimeType, - blockingExtension: resource.blockingExtension, - blockedReason: resource.blockedReason, - }); - break; - case "eventTimings": - // Total time doesn't have to be always set e.g. net provider enhancer - // in Console panel is using this method to fetch data when network log - // is expanded. So, make sure to not push undefined into the payload queue - // (it could overwrite an existing value). - if (typeof resource.totalTime !== "undefined") { - this.pushRequestToQueue(resource.actor, { - totalTime: resource.totalTime, - }); - } - break; + async onNetworkResourceUpdated(resource) { + // Identify the channel as SSE if mimeType is event-stream. + if (resource?.mimeType?.includes("text/event-stream")) { + await this.setEventStreamFlag(resource.actor); } - // This available field helps knowing when/if updateType property is arrived - // and can be requested via `requestData` - this.pushRequestToQueue(resource.actor, { - [`${update.updateType}Available`]: true, - }); - - await this.onPayloadDataReceived(resource); + this.pendingRequests.delete(resource.actor); + if (this.actionsEnabled && this.actions.updateRequest) { + await this.actions.updateRequest(resource.actor, resource, true); + } + // This event is fired only once per request, once all the properties are fetched + // from `onNetworkResourceUpdated`. There should be no more RDP requests after this. + // Note that this event might be consumed by extension so, emit it in production + // release as well. this.emitForTests(TEST_EVENTS.NETWORK_EVENT_UPDATED, resource.actor); + this.emit(EVENTS.PAYLOAD_READY, resource); } /** @@ -564,42 +437,6 @@ class FirefoxDataProvider { // TODO: Emit an event for test here } - /** - * Notify actions when events from onNetworkResourceUpdated are done, updated network event - * resources contain initial network info for each updateType and then we can invoke - * requestData to fetch its corresponded data lazily. - * Once all updateTypes of updated network event resource are available, we flush the merged - * request payload from pending queue and then update the component. - */ - async onPayloadDataReceived(resource) { - const payload = this.payloadQueue.get(resource.actor) || {}; - - // For blocked requests, we should only expect the request portions and not - // the response portions to be available. - if (!payload.requestHeadersAvailable || !payload.requestCookiesAvailable) { - return; - } - // For unblocked requests, we should wait for all major portions to be available. - if ( - !payload.blockedReason && - (!payload.eventTimingsAvailable || !payload.responseContentAvailable) - ) { - return; - } - - this.payloadQueue.delete(resource.actor); - - if (this.actionsEnabled && this.actions.updateRequest) { - await this.actions.updateRequest(resource.actor, payload, true); - } - - // This event is fired only once per request, once all the properties are fetched - // from `onNetworkResourceUpdated`. There should be no more RDP requests after this. - // Note that this event might be consumed by extension so, emit it in production - // release as well. - this.emit(EVENTS.PAYLOAD_READY, resource); - } - /** * Public connector API to lazily request HTTP details from the backend. * diff --git a/devtools/client/netmonitor/src/har/har-collector.js b/devtools/client/netmonitor/src/har/har-collector.js index 5d86e3cb1c44..b37bdbeca0e9 100644 --- a/devtools/client/netmonitor/src/har/har-collector.js +++ b/devtools/client/netmonitor/src/har/har-collector.js @@ -170,12 +170,7 @@ HarCollector.prototype = { for (const resource of resources) { trace.log("HarCollector.onNetworkEvent; ", resource); - const { - actor, - startedDateTime, - request: { method, url }, - isXHR, - } = resource; + const { actor, startedDateTime, method, url, isXHR } = resource; const startTime = Date.parse(startedDateTime); if (this.firstRequestStart == -1) { @@ -211,7 +206,7 @@ HarCollector.prototype = { }, onResourceUpdated: function(updates) { - for (const { resource, update } of updates) { + for (const { resource } of updates) { // Skip events from unknown actors (not in the list). // It can happen when there are zombie requests received after // the target is closed or multiple tabs are attached through @@ -221,84 +216,84 @@ HarCollector.prototype = { return; } - trace.log( - "HarCollector.onNetworkEventUpdate; " + update.updateType, - resource - ); - const includeResponseBodies = Services.prefs.getBoolPref( "devtools.netmonitor.har.includeResponseBodies" ); - let request; - switch (update.updateType) { - case "requestHeaders": - request = this.getData( - resource.actor, - "getRequestHeaders", - this.onRequestHeaders - ); - break; - case "requestCookies": - request = this.getData( - resource.actor, - "getRequestCookies", - this.onRequestCookies - ); - break; - case "requestPostData": - request = this.getData( - resource.actor, - "getRequestPostData", - this.onRequestPostData - ); - break; - case "responseHeaders": - request = this.getData( - resource.actor, - "getResponseHeaders", - this.onResponseHeaders - ); - break; - case "responseCookies": - request = this.getData( - resource.actor, - "getResponseCookies", - this.onResponseCookies - ); - break; - case "responseStart": - file.httpVersion = resource.response.httpVersion; - file.status = resource.response.status; - file.statusText = resource.response.statusText; - break; - case "responseContent": - file.contentSize = resource.contentSize; - file.mimeType = resource.mimeType; - file.transferredSize = resource.transferredSize; + [ + { + type: "eventTimings", + method: "getEventTimings", + callbackName: "onEventTimings", + }, + { + type: "requestHeaders", + method: "getRequestHeaders", + callbackName: "onRequestHeaders", + }, + { + type: "requestPostData", + method: "getRequestPostData", + callbackName: "onRequestPostData", + }, + { + type: "responseHeaders", + method: "getResponseHeaders", + callbackName: "onResponseHeaders", + }, + { type: "responseStart" }, + { + type: "responseContent", + method: "getResponseContent", + callbackName: "onResponseContent", + }, + { + type: "requestCookies", + method: "getRequestCookies", + callbackName: "onRequestCookies", + }, + { + type: "responseCookies", + method: "getResponseCookies", + callbackName: "onResponseCookies", + }, + ].forEach(updateType => { + trace.log( + "HarCollector.onNetworkEventUpdate; " + updateType.type, + resource + ); - if (includeResponseBodies) { + let request; + if (resource[`${updateType.type}Available`]) { + if (updateType.type == "responseStart") { + file.httpVersion = resource.httpVersion; + file.status = resource.status; + file.statusText = resource.statusText; + } else if (updateType.type == "responseContent") { + file.contentSize = resource.contentSize; + file.mimeType = resource.mimeType; + file.transferredSize = resource.transferredSize; + if (includeResponseBodies) { + request = this.getData( + resource.actor, + updateType.method, + this[updateType.callbackName] + ); + } + } else { request = this.getData( resource.actor, - "getResponseContent", - this.onResponseContent + updateType.method, + this[updateType.callbackName] ); } - break; - case "eventTimings": - request = this.getData( - resource.actor, - "getEventTimings", - this.onEventTimings - ); - break; - } + } - if (request) { - this.requests.push(request); - } - - this.resetPageLoadTimeout(); + if (request) { + this.requests.push(request); + } + this.resetPageLoadTimeout(); + }); } }, diff --git a/devtools/client/netmonitor/src/har/har-importer.js b/devtools/client/netmonitor/src/har/har-importer.js index 341662e1f6db..4f7d88b5f034 100644 --- a/devtools/client/netmonitor/src/har/har-importer.js +++ b/devtools/client/netmonitor/src/har/har-importer.js @@ -5,6 +5,9 @@ "use strict"; const { TIMING_KEYS } = require("devtools/client/netmonitor/src/constants"); +const { + getUrlDetails, +} = require("devtools/client/netmonitor/src/utils/request-utils"); var guid = 0; @@ -47,6 +50,7 @@ HarImporter.prototype = { startedMs: startedMs, method: entry.request.method, url: entry.request.url, + urlDetails: getUrlDetails(entry.request.url), isXHR: false, cause: { loadingDocumentUri: "", diff --git a/devtools/client/netmonitor/src/reducers/requests.js b/devtools/client/netmonitor/src/reducers/requests.js index 924bff8c9d03..c5fab4cb7bfd 100644 --- a/devtools/client/netmonitor/src/reducers/requests.js +++ b/devtools/client/netmonitor/src/reducers/requests.js @@ -5,7 +5,6 @@ "use strict"; const { - getUrlDetails, processNetworkUpdates, } = require("devtools/client/netmonitor/src/utils/request-utils"); const { @@ -152,11 +151,12 @@ function requestsReducer(state = Requests(), action) { function addRequest(state, action) { const nextState = { ...state }; - + // The target front is not used and cannot be serialized by redux + // eslint-disable-next-line no-unused-vars + const { targetFront, ...requestData } = action.data; const newRequest = { id: action.id, - ...action.data, - urlDetails: getUrlDetails(action.data.url), + ...requestData, }; nextState.requests = [...state.requests, newRequest]; diff --git a/devtools/client/netmonitor/src/utils/request-utils.js b/devtools/client/netmonitor/src/utils/request-utils.js index ce6b633e919a..08ca7e00142f 100644 --- a/devtools/client/netmonitor/src/utils/request-utils.js +++ b/devtools/client/netmonitor/src/utils/request-utils.js @@ -585,26 +585,25 @@ async function getMessagePayload(payload, getLongString) { /** * This helper function is used for additional processing of - * incoming network update packets. It's used by Network and - * Console panel reducers. + * incoming network update packets. It makes sure the only valid + * update properties and the values are correct. + * It's used by Network and Console panel reducers. + * @param {object} update + * The new update payload + * @param {object} request + * The current request in the state */ function processNetworkUpdates(update) { - const result = {}; + const newRequest = {}; for (const [key, value] of Object.entries(update)) { if (UPDATE_PROPS.includes(key)) { - result[key] = value; - - switch (key) { - case "totalTime": - result.totalTime = update.totalTime; - break; - case "requestPostData": - result.requestHeadersFromUploadStream = value.uploadHeaders; - break; + newRequest[key] = value; + if (key == "requestPostData") { + newRequest.requestHeadersFromUploadStream = value.uploadHeaders; } } } - return result; + return newRequest; } /** diff --git a/devtools/client/webconsole/components/Output/message-types/NetworkEventMessage.js b/devtools/client/webconsole/components/Output/message-types/NetworkEventMessage.js index 2321e2ebca90..69c57777dee0 100644 --- a/devtools/client/webconsole/components/Output/message-types/NetworkEventMessage.js +++ b/devtools/client/webconsole/components/Output/message-types/NetworkEventMessage.js @@ -80,16 +80,17 @@ function NetworkEventMessage({ source, type, level, - request, + url, + method, isXHR, timeStamp, blockedReason, + httpVersion, + status, + statusText, + totalTime, } = message; - const { response = {}, totalTime } = networkMessageUpdate; - - const { httpVersion, status, statusText } = response; - const topLevelClasses = ["cm-s-mozilla"]; if (isMessageNetworkError(message)) { topLevelClasses.push("error"); @@ -139,7 +140,7 @@ function NetworkEventMessage({ const onToggle = (messageId, e) => { const shouldOpenLink = (isMacOS && e.metaKey) || (!isMacOS && e.ctrlKey); if (shouldOpenLink) { - serviceContainer.openLink(request.url, e); + serviceContainer.openLink(url, e); e.stopPropagation(); } else if (open) { dispatch(actions.messageClose(messageId)); @@ -149,27 +150,24 @@ function NetworkEventMessage({ }; // Message body components. - const method = dom.span({ className: "method" }, request.method); + const requestMethod = dom.span({ className: "method" }, method); const xhr = isXHR ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator")) : null; - const requestUrl = dom.span( - { className: "url", title: request.url }, - request.url - ); + const requestUrl = dom.span({ className: "url", title: url }, url); const statusBody = statusInfo ? dom.a({ className: "status" }, statusInfo) : null; - const messageBody = [xhr, method, requestUrl, statusBody]; + const messageBody = [xhr, requestMethod, requestUrl, statusBody]; // API consumed by Net monitor UI components. Most of the method // are not needed in context of the Console panel (atm) and thus // let's just provide empty implementation. // Individual methods might be implemented step by step as needed. const connector = { - viewSourceInDebugger: (url, line, column) => { - serviceContainer.onViewSourceInDebugger({ url, line, column }); + viewSourceInDebugger: (srcUrl, line, column) => { + serviceContainer.onViewSourceInDebugger({ url: srcUrl, line, column }); }, getLongString: grip => { return serviceContainer.getLongString(grip); @@ -210,6 +208,7 @@ function NetworkEventMessage({ }) ); + const request = { url, method }; return Message({ dispatch, messageId: id, diff --git a/devtools/client/webconsole/enhancers/net-provider.js b/devtools/client/webconsole/enhancers/net-provider.js index 5f6e438b89c0..7279182d4573 100644 --- a/devtools/client/webconsole/enhancers/net-provider.js +++ b/devtools/client/webconsole/enhancers/net-provider.js @@ -51,9 +51,7 @@ function enableNetProvider(webConsoleUI) { const message = updates[action.id]; if (message && !message.openedOnce && message.source == "network") { dataProvider.onNetworkResourceAvailable(message); - message.updates.forEach(updateType => { - dataProvider.onNetworkResourceUpdated(message, { updateType }); - }); + dataProvider.onNetworkResourceUpdated(message); } } @@ -69,9 +67,7 @@ function enableNetProvider(webConsoleUI) { const open = allMessages.includes(actor); if (open) { const message = getMessage(newState, actor); - message.updates.forEach(updateType => { - dataProvider.onNetworkResourceUpdated(message, { updateType }); - }); + dataProvider.onNetworkResourceUpdated(message); } } } diff --git a/devtools/client/webconsole/reducers/messages.js b/devtools/client/webconsole/reducers/messages.js index 03046eb51d7a..1db15affeee3 100644 --- a/devtools/client/webconsole/reducers/messages.js +++ b/devtools/client/webconsole/reducers/messages.js @@ -1359,7 +1359,7 @@ function passSearchFilters(message, filters) { // Look for a match in location. isTextInFrame(matchStr, message.frame) || // Look for a match in net events. - isTextInNetEvent(matchStr, message.request) || + isTextInNetEvent(matchStr, message) || // Look for a match in stack-trace. isTextInStackTrace(matchStr, message.stacktrace) || // Look for a match in messageText. @@ -1445,12 +1445,10 @@ function isTextInParameter(matchStr, parameter) { /** * Returns true if given text is included in provided net event grip. */ -function isTextInNetEvent(matchStr, request) { - if (!request) { +function isTextInNetEvent(matchStr, { method, url } = {}) { + if (!method && !url) { return false; } - - const { method, url } = request; return matchStr(method) || matchStr(url); } diff --git a/devtools/client/webconsole/test/browser/browser_console_webconsole_iframe_messages.js b/devtools/client/webconsole/test/browser/browser_console_webconsole_iframe_messages.js index 8149d2ed5140..1ea572f8268a 100644 --- a/devtools/client/webconsole/test/browser/browser_console_webconsole_iframe_messages.js +++ b/devtools/client/webconsole/test/browser/browser_console_webconsole_iframe_messages.js @@ -32,6 +32,8 @@ add_task(async function() { // clear the browser console. await clearOutput(hud); + await waitForTick(); + await safeCloseBrowserConsole(); }); async function testMessages(hud) { diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js b/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js index 051dcc5f0b9b..308d39d53083 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js @@ -24,9 +24,9 @@ add_task(async function task() { const currentTab = gBrowser.selectedTab; const target = await TargetFactory.forTab(currentTab); const toolbox = gDevTools.getToolbox(target); + const panel = toolbox.getCurrentPanel().panelWin; - const monitor = toolbox.getCurrentPanel(); - const netReady = monitor.panelWin.api.once("NetMonitor:PayloadReady"); + const netReady = panel.api.once("NetMonitor:PayloadReady"); // Fire an XHR POST request. await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { @@ -46,19 +46,7 @@ add_task(async function task() { const urlNode = messageNode.querySelector(".url"); info("Network message found."); - const onReady = new Promise(resolve => { - let count = 0; - function onPayloadReady(updateCount) { - count += updateCount; - // Wait for all NETWORK_REQUEST updated events - // Otherwise we may still be having pending request - if (count == 7) { - hud.ui.off("network-request-payload-ready", onPayloadReady); - resolve(); - } - } - hud.ui.on("network-request-payload-ready", onPayloadReady); - }); + const onReady = hud.ui.once("network-request-payload-ready"); // Expand network log urlNode.click(); @@ -66,6 +54,7 @@ add_task(async function task() { await onReady; info("network-request-payload-ready received"); + await testNetworkMessage(messageNode); await waitForLazyRequests(toolbox); }); diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_shows_reqs_from_netmonitor.js b/devtools/client/webconsole/test/browser/browser_webconsole_shows_reqs_from_netmonitor.js index 63dc55e5495c..28a891cf831e 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_shows_reqs_from_netmonitor.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_shows_reqs_from_netmonitor.js @@ -27,6 +27,9 @@ registerCleanupFunction(async () => { }); add_task(async function task() { + // Make sure the filter to show all the requests is set + await pushPref("devtools.netmonitor.filters", '["all"]'); + // Test that the request appears in the console. const hud = await openNewTabAndConsole(TEST_URI); const currentTab = gBrowser.selectedTab; @@ -65,11 +68,14 @@ async function testNetmonitor(toolbox) { ); store.dispatch(Actions.batchEnable(false)); - const requestItem = document.querySelector(".request-list-item"); + await waitUntil(() => store.getState().requests.requests.length > 0); // Lets also wait until all the event timings data requested // has completed and the column is rendered. - await waitForDOM(requestItem, ".requests-list-timings-total"); + await waitForDOM( + document, + ".request-list-item:first-child .requests-list-timings-total" + ); is( store.getState().requests.requests.length, diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_stubs_network_event.js b/devtools/client/webconsole/test/browser/browser_webconsole_stubs_network_event.js index d733a4b5e9f0..8eeea50d23a3 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_stubs_network_event.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_stubs_network_event.js @@ -40,15 +40,7 @@ add_task(async function() { let failed = false; for (const [key, packet] of generatedStubs) { - // packet.updates are handle by the webconsole front, and can be updated after - // we cleaned the packet, so the order isn't guaranteed. Let's sort the array - // here so the test doesn't fail. const existingPacket = existingStubs.stubPackets.get(key); - if (packet.updates && existingPacket.updates) { - packet.updates.sort(); - existingPacket.updates.sort(); - } - const packetStr = JSON.stringify(packet, null, 2); const existingPacketStr = JSON.stringify(existingPacket, null, 2); is(packetStr, existingPacketStr, `"${key}" packet has expected value`); @@ -67,7 +59,6 @@ async function generateNetworkEventStubs() { const tab = await addTab(TEST_URI); const resourceWatcher = await createResourceWatcherForTab(tab); const stacktraces = new Map(); - let addNetworkStub = function() {}; let addNetworkUpdateStub = function() {}; @@ -110,7 +101,6 @@ async function generateNetworkEventStubs() { ); for (const [key, code] of getCommands()) { - const noExpectedUpdates = 7; const networkEventDone = new Promise(resolve => { addNetworkStub = resource => { stubs.set(key, getCleanedPacket(key, getOrderedResource(resource))); @@ -118,25 +108,17 @@ async function generateNetworkEventStubs() { }; }); const networkEventUpdateDone = new Promise(resolve => { - let updateCount = 0; addNetworkUpdateStub = resource => { const updateKey = `${key} update`; - // make sure all the updates have been happened - if (updateCount >= noExpectedUpdates) { - // make sure the network event stub contains all the updates - stubs.set(key, getCleanedPacket(key, getOrderedResource(resource))); - stubs.set( - updateKey, - // We cannot ensure the form of the resource, some properties - // might be in another order than in the original resource. - // Hand-picking only what we need should prevent this. - getCleanedPacket(updateKey, getOrderedResource(resource)) - ); - - resolve(); - } else { - updateCount++; - } + stubs.set(key, getCleanedPacket(key, getOrderedResource(resource))); + stubs.set( + updateKey, + // We cannot ensure the form of the resource, some properties + // might be in another order than in the original resource. + // Hand-picking only what we need should prevent this. + getCleanedPacket(updateKey, getOrderedResource(resource)) + ); + resolve(); }; }); @@ -169,15 +151,23 @@ async function generateNetworkEventStubs() { function getOrderedResource(resource) { return { resourceType: resource.resourceType, - _type: resource._type, timeStamp: resource.timeStamp, - node: resource.node, actor: resource.actor, startedDateTime: resource.startedDateTime, - request: resource.request, + method: resource.method, + url: resource.url, isXHR: resource.isXHR, cause: resource.cause, - response: resource.response, + httpVersion: resource.httpVersion, + status: resource.status, + statusText: resource.statusText, + headersSize: resource.headersSize, + remoteAddress: resource.remoteAddress, + remotePort: resource.remotePort, + mimeType: resource.mimeType, + waitingTime: resource.waitingTime, + contentSize: resource.contentSize, + transferredSize: resource.transferredSize, timings: resource.timings, private: resource.private, fromCache: resource.fromCache, @@ -185,10 +175,11 @@ function getOrderedResource(resource) { isThirdPartyTrackingResource: resource.isThirdPartyTrackingResource, referrerPolicy: resource.referrerPolicy, blockedReason: resource.blockedReason, + blockingExtension: resource.blockingExtension, channelId: resource.channelId, - updates: resource.updates, totalTime: resource.totalTime, securityState: resource.securityState, + responseCache: resource.responseCache, isRacing: resource.isRacing, }; } diff --git a/devtools/client/webconsole/test/browser/head.js b/devtools/client/webconsole/test/browser/head.js index f31d06155b5f..d4e445ffa886 100644 --- a/devtools/client/webconsole/test/browser/head.js +++ b/devtools/client/webconsole/test/browser/head.js @@ -1704,7 +1704,12 @@ function toggleLayout(hud) { async function waitForLazyRequests(toolbox) { const { wrapper } = toolbox.getCurrentPanel().hud.ui; return waitUntil(() => { - return !wrapper.networkDataProvider.lazyRequestData.size; + return ( + !wrapper.networkDataProvider.lazyRequestData.size && + // Make sure that batched request updates are all complete + // as they trigger late lazy data requests. + !wrapper.queuedRequestUpdates.length + ); }); } diff --git a/devtools/client/webconsole/test/browser/stub-generator-helpers.js b/devtools/client/webconsole/test/browser/stub-generator-helpers.js index ba18ae6805c6..c5e01ad9a46f 100644 --- a/devtools/client/webconsole/test/browser/stub-generator-helpers.js +++ b/devtools/client/webconsole/test/browser/stub-generator-helpers.js @@ -296,29 +296,8 @@ function getCleanedPacket(key, packet) { res.actor = existingPacket.actor; } - if (res?.request?.headersSize && existingPacket?.request?.headersSize) { - res.request.headersSize = existingPacket.request.headersSize; - } - - if (res?.response?.headersSize && existingPacket?.response?.headersSize) { - res.response.headersSize = existingPacket.response.headersSize; - } - if (res?.response?.bodySize && existingPacket?.response?.bodySize) { - res.response.bodySize = existingPacket.response.bodySize; - } - if ( - res?.response?.transferredSize && - existingPacket?.response?.transferredSize - ) { - res.response.transferredSize = existingPacket.response.transferredSize; - } - - if (res?.response?.waitingTime && existingPacket?.response?.waitingTime) { - res.response.waitingTime = existingPacket.response.waitingTime; - } - - if (res.updates && Array.isArray(res.updates)) { - res.updates.sort(); + if (res.waitingTime && existingPacket.waitingTime) { + res.waitingTime = existingPacket.waitingTime; } if (res.helperResult) { diff --git a/devtools/client/webconsole/test/node/fixtures/stubs/networkEvent.js b/devtools/client/webconsole/test/node/fixtures/stubs/networkEvent.js index 8cc4a9e87f26..8785051f54ff 100644 --- a/devtools/client/webconsole/test/node/fixtures/stubs/networkEvent.js +++ b/devtools/client/webconsole/test/node/fixtures/stubs/networkEvent.js @@ -23,15 +23,11 @@ rawPackets.set(`GET request`, { "timeStamp": 1572867483805, "actor": "server0.conn0.netEvent4", "startedDateTime": "2019-11-04T11:06:34.542Z", - "request": { - "url": "http://example.com/inexistent.html", - "method": "GET", - "headersSize": 385 - }, + "method": "GET", + "url": "http://example.com/inexistent.html", "isXHR": false, "cause": { "type": "img", - "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "stacktraceAvailable": true, "lastFrame": { "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", @@ -41,34 +37,20 @@ rawPackets.set(`GET request`, { "asyncCause": null } }, - "response": { - "httpVersion": "HTTP/1.1", - "status": "404", - "statusText": "Not Found", - "headersSize": 160, - "remoteAddress": "127.0.0.1", - "remotePort": 8888, - "content": { - "mimeType": "text/html; charset=utf-8" - }, - "waitingTime": 1, - "bodySize": 418, - "transferredSize": 578 - }, + "httpVersion": "HTTP/1.1", + "status": "404", + "statusText": "Not Found", + "remoteAddress": "127.0.0.1", + "remotePort": 8888, + "mimeType": "text/html; charset=utf-8", + "waitingTime": 1, + "contentSize": 418, + "transferredSize": 578, "timings": {}, "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", - "updates": [ - "eventTimings", - "requestCookies", - "requestHeaders", - "responseContent", - "responseCookies", - "responseHeaders", - "responseStart", - "securityInfo" - ], + "blockedReason": 0, "totalTime": 2, "securityState": "insecure", "isRacing": false @@ -79,15 +61,11 @@ rawPackets.set(`GET request update`, { "timeStamp": 1572867483805, "actor": "server0.conn0.netEvent5", "startedDateTime": "2020-07-07T14:41:14.572Z", - "request": { - "url": "http://example.com/inexistent.html", - "method": "GET", - "headersSize": 385 - }, + "method": "GET", + "url": "http://example.com/inexistent.html", "isXHR": false, "cause": { "type": "img", - "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "stacktraceAvailable": true, "lastFrame": { "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", @@ -97,34 +75,20 @@ rawPackets.set(`GET request update`, { "asyncCause": null } }, - "response": { - "httpVersion": "HTTP/1.1", - "status": "404", - "statusText": "Not Found", - "headersSize": 160, - "remoteAddress": "127.0.0.1", - "remotePort": 8888, - "content": { - "mimeType": "text/html; charset=utf-8" - }, - "waitingTime": 1, - "bodySize": 418, - "transferredSize": 578 - }, + "httpVersion": "HTTP/1.1", + "status": "404", + "statusText": "Not Found", + "remoteAddress": "127.0.0.1", + "remotePort": 8888, + "mimeType": "text/html; charset=utf-8", + "waitingTime": 1, + "contentSize": 418, + "transferredSize": 578, "timings": {}, "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", - "updates": [ - "eventTimings", - "requestCookies", - "requestHeaders", - "responseContent", - "responseCookies", - "responseHeaders", - "responseStart", - "securityInfo" - ], + "blockedReason": 0, "totalTime": 3, "securityState": "insecure", "isRacing": false @@ -135,15 +99,11 @@ rawPackets.set(`XHR GET request`, { "timeStamp": 1572867483805, "actor": "server0.conn0.netEvent21", "startedDateTime": "2020-07-07T14:41:14.612Z", - "request": { - "url": "http://example.com/inexistent.html", - "method": "GET", - "headersSize": 385 - }, + "method": "GET", + "url": "http://example.com/inexistent.html", "isXHR": true, "cause": { "type": "xhr", - "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "stacktraceAvailable": true, "lastFrame": { "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", @@ -153,34 +113,20 @@ rawPackets.set(`XHR GET request`, { "asyncCause": null } }, - "response": { - "httpVersion": "HTTP/1.1", - "status": "404", - "statusText": "Not Found", - "headersSize": 160, - "remoteAddress": "127.0.0.1", - "remotePort": 8888, - "content": { - "mimeType": "text/html; charset=utf-8" - }, - "waitingTime": 2, - "bodySize": 418, - "transferredSize": 578 - }, + "httpVersion": "HTTP/1.1", + "status": "404", + "statusText": "Not Found", + "remoteAddress": "127.0.0.1", + "remotePort": 8888, + "mimeType": "text/html; charset=utf-8", + "waitingTime": 1, + "contentSize": 418, + "transferredSize": 578, "timings": {}, "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", - "updates": [ - "eventTimings", - "requestCookies", - "requestHeaders", - "responseContent", - "responseCookies", - "responseHeaders", - "responseStart", - "securityInfo" - ], + "blockedReason": 0, "totalTime": 1, "securityState": "insecure", "isRacing": false @@ -190,15 +136,11 @@ rawPackets.set(`XHR GET request update`, { "resourceType": "network-event", "timeStamp": 1572867483805, "actor": "server0.conn0.netEvent20", - "request": { - "url": "http://example.com/inexistent.html", - "method": "GET", - "headersSize": 385 - }, + "method": "GET", + "url": "http://example.com/inexistent.html", "isXHR": true, "cause": { "type": "xhr", - "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "stacktraceAvailable": true, "lastFrame": { "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", @@ -208,34 +150,20 @@ rawPackets.set(`XHR GET request update`, { "asyncCause": null } }, - "response": { - "httpVersion": "HTTP/1.1", - "status": "404", - "statusText": "Not Found", - "headersSize": 160, - "remoteAddress": "127.0.0.1", - "remotePort": 8888, - "content": { - "mimeType": "text/html; charset=utf-8" - }, - "waitingTime": 2, - "bodySize": 418, - "transferredSize": 578 - }, + "httpVersion": "HTTP/1.1", + "status": "404", + "statusText": "Not Found", + "remoteAddress": "127.0.0.1", + "remotePort": 8888, + "mimeType": "text/html; charset=utf-8", + "waitingTime": 1, + "contentSize": 418, + "transferredSize": 578, "timings": {}, "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", - "updates": [ - "eventTimings", - "requestCookies", - "requestHeaders", - "responseContent", - "responseCookies", - "responseHeaders", - "responseStart", - "securityInfo" - ], + "blockedReason": 0, "totalTime": 1, "securityState": "insecure", "isRacing": false @@ -246,15 +174,11 @@ rawPackets.set(`XHR POST request`, { "timeStamp": 1572867483805, "actor": "server0.conn0.netEvent36", "startedDateTime": "2019-11-04T11:06:35.007Z", - "request": { - "url": "http://example.com/inexistent.html", - "method": "POST", - "headersSize": 385 - }, + "method": "POST", + "url": "http://example.com/inexistent.html", "isXHR": true, "cause": { "type": "xhr", - "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "stacktraceAvailable": true, "lastFrame": { "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", @@ -264,34 +188,20 @@ rawPackets.set(`XHR POST request`, { "asyncCause": null } }, - "response": { - "httpVersion": "HTTP/1.1", - "status": "404", - "statusText": "Not Found", - "headersSize": 160, - "remoteAddress": "127.0.0.1", - "remotePort": 8888, - "content": { - "mimeType": "text/html; charset=utf-8" - }, - "waitingTime": 2, - "bodySize": 418, - "transferredSize": 578 - }, + "httpVersion": "HTTP/1.1", + "status": "404", + "statusText": "Not Found", + "remoteAddress": "127.0.0.1", + "remotePort": 8888, + "mimeType": "text/html; charset=utf-8", + "waitingTime": 1, + "contentSize": 418, + "transferredSize": 578, "timings": {}, "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", - "updates": [ - "eventTimings", - "requestCookies", - "requestHeaders", - "responseContent", - "responseCookies", - "responseHeaders", - "responseStart", - "securityInfo" - ], + "blockedReason": 0, "totalTime": 1, "securityState": "insecure", "isRacing": false @@ -301,15 +211,11 @@ rawPackets.set(`XHR POST request update`, { "resourceType": "network-event", "timeStamp": 1572867483805, "actor": "server0.conn0.netEvent36", - "request": { - "url": "http://example.com/inexistent.html", - "method": "POST", - "headersSize": 385 - }, + "method": "POST", + "url": "http://example.com/inexistent.html", "isXHR": true, "cause": { "type": "xhr", - "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", "stacktraceAvailable": true, "lastFrame": { "filename": "http://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html", @@ -319,34 +225,20 @@ rawPackets.set(`XHR POST request update`, { "asyncCause": null } }, - "response": { - "httpVersion": "HTTP/1.1", - "status": "404", - "statusText": "Not Found", - "headersSize": 160, - "remoteAddress": "127.0.0.1", - "remotePort": 8888, - "content": { - "mimeType": "text/html; charset=utf-8" - }, - "waitingTime": 2, - "bodySize": 418, - "transferredSize": 578 - }, + "httpVersion": "HTTP/1.1", + "status": "404", + "statusText": "Not Found", + "remoteAddress": "127.0.0.1", + "remotePort": 8888, + "mimeType": "text/html; charset=utf-8", + "waitingTime": 1, + "contentSize": 418, + "transferredSize": 578, "timings": {}, "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", - "updates": [ - "eventTimings", - "requestCookies", - "requestHeaders", - "responseContent", - "responseCookies", - "responseHeaders", - "responseStart", - "securityInfo" - ], + "blockedReason": 0, "totalTime": 2, "securityState": "insecure", "isRacing": false diff --git a/devtools/client/webconsole/utils/messages.js b/devtools/client/webconsole/utils/messages.js index 7cb01a6da2cc..67a76911e020 100644 --- a/devtools/client/webconsole/utils/messages.js +++ b/devtools/client/webconsole/utils/messages.js @@ -6,9 +6,6 @@ const Services = require("Services"); const l10n = require("devtools/client/webconsole/utils/l10n"); -const { - getUrlDetails, -} = require("devtools/client/netmonitor/src/utils/request-utils"); const { ResourceWatcher, } = require("devtools/shared/resources/resource-watcher"); @@ -362,24 +359,7 @@ function transformCSSMessageResource(cssMessageResource) { } function transformNetworkEventResource(networkEventResource) { - return new NetworkEventMessage({ - targetFront: networkEventResource.targetFront, - actor: networkEventResource.actor, - isXHR: networkEventResource.isXHR, - request: networkEventResource.request, - response: networkEventResource.response, - timeStamp: networkEventResource.timeStamp, - totalTime: networkEventResource.totalTime, - url: networkEventResource.request.url, - urlDetails: getUrlDetails(networkEventResource.request.url), - method: networkEventResource.request.method, - updates: networkEventResource.updates, - cause: networkEventResource.cause, - private: networkEventResource.private, - securityState: networkEventResource.securityState, - chromeContext: networkEventResource.chromeContext, - blockedReason: networkEventResource.blockedReason, - }); + return new NetworkEventMessage(networkEventResource); } function transformEvaluationResultPacket(packet) { @@ -790,8 +770,8 @@ function getNaturalOrder(messageA, messageB) { function isMessageNetworkError(message) { return ( message.source === MESSAGE_SOURCE.NETWORK && - message?.response?.status && - message.response.status.toString().match(/^[4,5]\d\d$/) + message?.status && + message?.status.toString().match(/^[4,5]\d\d$/) ); } diff --git a/devtools/client/webconsole/webconsole-ui.js b/devtools/client/webconsole/webconsole-ui.js index cfade079ef13..5a4d3fc58d3b 100644 --- a/devtools/client/webconsole/webconsole-ui.js +++ b/devtools/client/webconsole/webconsole-ui.js @@ -423,32 +423,13 @@ class WebConsoleUI { } _onResourceUpdated(updates) { - const messages = []; - for (const { resource } of updates) { - if ( - resource.resourceType == this.hud.resourceWatcher.TYPES.NETWORK_EVENT - ) { - // network-message-updated will emit when all the update message arrives. - // Since we can't ensure the order of the network update, we check - // that message.updates has all we need. - // Note that 'requestPostData' is sent only for POST requests, so we need - // to count with that. - const NUMBER_OF_NETWORK_UPDATE = 8; - - let expectedLength = NUMBER_OF_NETWORK_UPDATE; - if (resource.updates.includes("responseCache")) { - expectedLength++; - } - if (resource.updates.includes("requestPostData")) { - expectedLength++; - } - - if (resource.updates.length === expectedLength) { - messages.push(resource); - } - } - } - this.wrapper.dispatchMessagesUpdate(messages); + const messageUpdates = updates + .filter( + ({ resource }) => + resource.resourceType == this.hud.resourceWatcher.TYPES.NETWORK_EVENT + ) + .map(({ resource }) => resource); + this.wrapper.dispatchMessagesUpdate(messageUpdates); } /** diff --git a/devtools/shared/resources/legacy-listeners/network-events.js b/devtools/shared/resources/legacy-listeners/network-events.js index 551ab7347fc7..5ee3d9d9fb86 100644 --- a/devtools/shared/resources/legacy-listeners/network-events.js +++ b/devtools/shared/resources/legacy-listeners/network-events.js @@ -30,90 +30,80 @@ module.exports = async function({ } const webConsoleFront = await targetFront.getFront("console"); - const _resources = new Map(); + const resources = new Map(); function onNetworkEvent(packet) { const actor = packet.eventActor; - const resource = { + + resources.set(actor.actor, { resourceId: actor.channelId, resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, - timeStamp: actor.timeStamp, - actor: actor.actor, - startedDateTime: actor.startedDateTime, - request: { + isBlocked: !!actor.blockedReason, + types: [], + resourceUpdates: {}, + }); + + onAvailable([ + { + resourceId: actor.channelId, + resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, + timeStamp: actor.timeStamp, + actor: actor.actor, + startedDateTime: actor.startedDateTime, url: actor.url, method: actor.method, + isXHR: actor.isXHR, + cause: { type: actor.cause.type }, + timings: {}, + private: actor.private, + fromCache: actor.fromCache, + fromServiceWorker: actor.fromServiceWorker, + isThirdPartyTrackingResource: actor.isThirdPartyTrackingResource, + referrerPolicy: actor.referrerPolicy, + blockedReason: actor.blockedReason, + blockingExtension: actor.blockingExtension, + stacktraceResourceId: + actor.cause.type == "websocket" + ? actor.url.replace(/^http/, "ws") + : actor.channelId, }, - isXHR: actor.isXHR, - cause: actor.cause, - response: {}, - timings: {}, - private: actor.private, - fromCache: actor.fromCache, - fromServiceWorker: actor.fromServiceWorker, - isThirdPartyTrackingResource: actor.isThirdPartyTrackingResource, - referrerPolicy: actor.referrerPolicy, - blockedReason: actor.blockedReason, - blockingExtension: actor.blockingExtension, - stacktraceResourceId: - actor.cause.type == "websocket" - ? actor.url.replace(/^http/, "ws") - : actor.channelId, - updates: [], - }; - - // Lets remove the stacktrace info here as - // it is passed from the the server by the NETWORK_EVENT_STACKTRACE - // resource type. - delete resource.cause.stacktraceAvailable; - delete resource.cause.lastFrame; - - _resources.set(actor.actor, resource); - onAvailable([resource]); + ]); } function onNetworkEventUpdate(packet) { - const resource = _resources.get(packet.from); + const resource = resources.get(packet.from); if (!resource) { return; } - const updateType = packet.updateType; - const resourceUpdates = {}; - resourceUpdates.updates = [...resource.updates, updateType]; + const { + types, + resourceUpdates, + resourceId, + resourceType, + isBlocked, + } = resource; - switch (updateType) { - case "requestHeaders": - resourceUpdates.request = Object.assign({}, resource.request, { - headersSize: packet.headersSize, - }); - break; + switch (packet.updateType) { case "requestPostData": - resourceUpdates.request = Object.assign({}, resource.request, { - bodySize: packet.dataSize, - }); + resourceUpdates.contentSize = packet.dataSize; break; case "responseStart": - resourceUpdates.response = Object.assign({}, resource.response, { - httpVersion: packet.response.httpVersion, - status: packet.response.status, - statusText: packet.response.statusText, - headersSize: packet.response.headersSize, - remoteAddress: packet.response.remoteAddress, - remotePort: packet.response.remotePort, - content: { - mimeType: packet.response.mimeType, - }, - waitingTime: packet.response.waitingTime, - }); + resourceUpdates.httpVersion = packet.response.httpVersion; + resourceUpdates.status = packet.response.status; + resourceUpdates.statusText = packet.response.statusText; + resourceUpdates.remoteAddress = packet.response.remoteAddress; + resourceUpdates.remotePort = packet.response.remotePort; + resourceUpdates.mimeType = packet.response.mimeType; + resourceUpdates.waitingTime = packet.response.waitingTime; break; case "responseContent": - resourceUpdates.response = Object.assign({}, resource.response, { - bodySize: packet.contentSize, - transferredSize: packet.transferredSize, - content: { mimeType: packet.mimeType }, - }); + resourceUpdates.contentSize = packet.contentSize; + resourceUpdates.transferredSize = packet.transferredSize; + resourceUpdates.mimeType = packet.mimeType; + resourceUpdates.blockingExtension = packet.blockingExtension; + resourceUpdates.blockedReason = packet.blockedReason; break; case "eventTimings": resourceUpdates.totalTime = packet.totalTime; @@ -123,40 +113,40 @@ module.exports = async function({ resourceUpdates.isRacing = packet.isRacing; break; case "responseCache": - resourceUpdates.response = Object.assign({}, resource.response, { - responseCache: packet.responseCache, - }); + resourceUpdates.responseCache = packet.responseCache; break; } - // Update local resource. - Object.assign(resource, resourceUpdates); + resourceUpdates[`${packet.updateType}Available`] = true; + types.push(packet.updateType); + + if (isBlocked) { + // Blocked requests + if ( + !types.includes("requestHeaders") || + !types.includes("requestCookies") + ) { + return; + } + } else if ( + // Un-blocked requests + !types.includes("requestHeaders") || + !types.includes("requestCookies") || + !types.includes("eventTimings") || + !types.includes("responseContent") + ) { + return; + } onUpdated([ { - resourceType: resource.resourceType, - resourceId: resource.resourceId, + resourceType, + resourceId, resourceUpdates, - updateType, }, ]); - if (resource.blockedReason) { - // Blocked requests - if ( - resource.updates.includes("requestHeaders") && - resource.updates.includes("requestCookies") - ) { - _resources.delete(resource.actor); - } - } else if ( - resource.updates.includes("requestHeaders") && - resource.updates.includes("requestCookies") && - resource.updates.includes("eventTimings") && - resource.updates.includes("responseContent") - ) { - _resources.delete(resource.actor); - } + resources.delete(resource.actor); } webConsoleFront.on("serverNetworkEvent", onNetworkEvent); diff --git a/devtools/shared/resources/resource-watcher.js b/devtools/shared/resources/resource-watcher.js index b64572cd8d7f..027090af1ba3 100644 --- a/devtools/shared/resources/resource-watcher.js +++ b/devtools/shared/resources/resource-watcher.js @@ -847,4 +847,6 @@ const ResourceTransformers = { .ROOT_NODE]: require("devtools/shared/resources/transformers/root-node"), [ResourceWatcher.TYPES .SESSION_STORAGE]: require("devtools/shared/resources/transformers/storage-session-storage.js"), + [ResourceWatcher.TYPES + .NETWORK_EVENT]: require("devtools/shared/resources/transformers/network-events"), }; diff --git a/devtools/shared/resources/tests/browser.ini b/devtools/shared/resources/tests/browser.ini index 73a4c6b11f58..99b910ff8434 100644 --- a/devtools/shared/resources/tests/browser.ini +++ b/devtools/shared/resources/tests/browser.ini @@ -35,7 +35,6 @@ support-files = [browser_resources_getAllResources.js] [browser_resources_network_event_stacktraces.js] [browser_resources_network_events.js] -skip-if = os == "linux" #Bug 1655183 [browser_resources_platform_messages.js] [browser_resources_root_node.js] [browser_resources_several_resources.js] diff --git a/devtools/shared/resources/tests/browser_resources_network_events.js b/devtools/shared/resources/tests/browser_resources_network_events.js index e7aaef8368fb..50a11143876d 100644 --- a/devtools/shared/resources/tests/browser_resources_network_events.js +++ b/devtools/shared/resources/tests/browser_resources_network_events.js @@ -18,62 +18,35 @@ add_task(async function() { await testNetworkEventResourcesWithExistingResources(); await testNetworkEventResourcesWithoutExistingResources(); - // These tests would be enabled when the server-side work is done. See Bug 1644191 // info("Test network events server listener"); // await pushPref("devtools.testing.enableServerWatcherSupport", true); - // await testNetworkEventResources(); - // await testNetworkEventResourcesWithIgnoreExistingResources(); + // await testNetworkEventResourcesWithExistingResources(); + // await testNetworkEventResourcesWithoutExistingResources(); }); -const UPDATES = [ - "requestHeaders", - "requestCookies", - "responseStart", - "securityInfo", - "responseHeaders", - "responseCookies", - "eventTimings", - "responseContent", -]; - async function testNetworkEventResourcesWithExistingResources() { info(`Tests for network event resources with the existing resources`); await testNetworkEventResources({ ignoreExistingResources: false, // 1 available event fired, for the existing resource in the cache. // 1 available event fired, when live request is created. - expectedOnAvailableCounts: 2, - // 8 update events fired, when live request is updated. - expectedOnUpdatedCounts: 8, + totalExpectedOnAvailableCounts: 2, + // 1 update events fired, when live request is updated. + totalExpectedOnUpdatedCounts: 1, expectedResourcesOnAvailable: { - [`${EXAMPLE_DOMAIN}existing_post.html`]: { + [`${EXAMPLE_DOMAIN}cached_post.html`]: { resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, - request: { - url: `${EXAMPLE_DOMAIN}existing_post.html`, - method: "POST", - }, - // gets reset based on the type of request - updates: [], + method: "POST", }, [`${EXAMPLE_DOMAIN}live_get.html`]: { resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, - request: { - url: `${EXAMPLE_DOMAIN}live_get.html`, - method: "GET", - }, - // Throttling makes us receive the available event - // after processing all the updates events - updates: UPDATES, + method: "GET", }, }, expectedResourcesOnUpdated: { [`${EXAMPLE_DOMAIN}live_get.html`]: { resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, - request: { - url: `${EXAMPLE_DOMAIN}live_get.html`, - method: "GET", - }, - updates: UPDATES, + method: "GET", }, }, }); @@ -84,29 +57,19 @@ async function testNetworkEventResourcesWithoutExistingResources() { await testNetworkEventResources({ ignoreExistingResources: true, // 1 available event fired, when live request is created. - expectedOnAvailableCounts: 1, - // 8 update events fired, when live request is updated. - expectedOnUpdatedCounts: 8, + totalExpectedOnAvailableCounts: 1, + // 1 update events fired, when live request is updated. + totalExpectedOnUpdatedCounts: 1, expectedResourcesOnAvailable: { [`${EXAMPLE_DOMAIN}live_get.html`]: { resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, - request: { - url: `${EXAMPLE_DOMAIN}live_get.html`, - method: "GET", - }, - // Throttling makes us receive the available event - // after processing all the updates events - updates: UPDATES, + method: "GET", }, }, expectedResourcesOnUpdated: { [`${EXAMPLE_DOMAIN}live_get.html`]: { resourceType: ResourceWatcher.TYPES.NETWORK_EVENT, - request: { - url: `${EXAMPLE_DOMAIN}live_get.html`, - method: "GET", - }, - updates: UPDATES, + method: "GET", }, }, }); @@ -118,54 +81,34 @@ async function testNetworkEventResources(options) { tab ); - const actualResourcesOnAvailable = {}; - const actualResourcesOnUpdated = {}; - info( `Trigger some network requests *before* calling ResourceWatcher.watchResources in order to assert the behavior of already existing network events.` ); + let onResourceAvailable = () => {}; let onResourceUpdated = () => {}; - const waitOnAllExpectedUpdatesForExistingRequests = new Promise(resolve => { - const existingRequestUrl = `${EXAMPLE_DOMAIN}existing_post.html`; + // Lets make sure there is already a network event resource in the cache. + const waitOnRequestForResourceWatcherCache = new Promise(resolve => { onResourceAvailable = resources => { for (const resource of resources) { - // A blocked request would only have two updates so lets also resolve here - if ( - resource.request.url == existingRequestUrl && - resource.blockedReason && - resource.updates.length == 2 - ) { - // Reset the updates expectation as the request is blocked - if (options.expectedResourcesOnAvailable[resource.request.url]) { - options.expectedResourcesOnAvailable[ - resource.request.url - ].updates = [...resource.updates]; - } - resolve(); - } + is( + resource.resourceType, + ResourceWatcher.TYPES.NETWORK_EVENT, + "Received a network event resource" + ); } }; onResourceUpdated = updates => { for (const { resource } of updates) { - // Wait until all the update events have fired for the existing request. - // Handle both blocked and unblocked requests - if ( - resource.request.url == existingRequestUrl && - (resource.updates.length == 8 || - (resource.blockedReason && resource.updates.length == 2)) - ) { - // Makes sure the expectation always correct (for either blocked or unblocked requests) - if (options.expectedResourcesOnAvailable[resource.request.url]) { - options.expectedResourcesOnAvailable[ - resource.request.url - ].updates = [...resource.updates]; - } - resolve(); - } + is( + resource.resourceType, + ResourceWatcher.TYPES.NETWORK_EVENT, + "Received a network update event resource" + ); + resolve(); } }; @@ -177,23 +120,29 @@ async function testNetworkEventResources(options) { .then(() => { // We can only trigger the requests once `watchResources` settles, otherwise the // thread might be paused. - triggerNetworkRequests(tab.linkedBrowser, EXISTING_REQUESTS_COMMANDS); + triggerNetworkRequests(tab.linkedBrowser, [cachedRequest]); }); }); - await waitOnAllExpectedUpdatesForExistingRequests; + await waitOnRequestForResourceWatcherCache; + + const actualResourcesOnAvailable = {}; + const actualResourcesOnUpdated = {}; let { - expectedOnAvailableCounts, - expectedOnUpdatedCounts, + totalExpectedOnAvailableCounts, + totalExpectedOnUpdatedCounts, + expectedResourcesOnAvailable, + expectedResourcesOnUpdated, + ignoreExistingResources, } = options; - const waitForAllOnAvailableEvents = waitUntil( - () => expectedOnAvailableCounts == 0 + const waitForAllExpectedOnAvailableEvents = waitUntil( + () => totalExpectedOnAvailableCounts == 0 ); - const waitForAllOnUpdatedEvents = waitUntil( - () => expectedOnUpdatedCounts == 0 + const waitForAllExpectedOnUpdatedEvents = waitUntil( + () => totalExpectedOnUpdatedCounts == 0 ); const onAvailable = resources => { @@ -203,13 +152,12 @@ async function testNetworkEventResources(options) { ResourceWatcher.TYPES.NETWORK_EVENT, "Received a network event resource" ); - actualResourcesOnAvailable[resource.request.url] = { + actualResourcesOnAvailable[resource.url] = { resourceId: resource.resourceId, resourceType: resource.resourceType, - request: resource.request, - updates: [...resource.updates], + method: resource.method, }; - expectedOnAvailableCounts--; + totalExpectedOnAvailableCounts--; } }; @@ -220,13 +168,12 @@ async function testNetworkEventResources(options) { ResourceWatcher.TYPES.NETWORK_EVENT, "Received a network update event resource" ); - actualResourcesOnUpdated[resource.request.url] = { + actualResourcesOnUpdated[resource.url] = { resourceId: resource.resourceId, resourceType: resource.resourceType, - request: resource.request, - updates: [...resource.updates], + method: resource.method, }; - expectedOnUpdatedCounts--; + totalExpectedOnUpdatedCounts--; } }; @@ -240,14 +187,17 @@ async function testNetworkEventResources(options) { `Trigger the rest of the requests *after* calling ResourceWatcher.watchResources in order to assert the behavior of live network events.` ); - await triggerNetworkRequests(tab.linkedBrowser, LIVE_REQUESTS_COMMANDS); + await triggerNetworkRequests(tab.linkedBrowser, [liveRequest]); - await Promise.all([waitForAllOnAvailableEvents, waitForAllOnUpdatedEvents]); + await Promise.all([ + waitForAllExpectedOnAvailableEvents, + waitForAllExpectedOnUpdatedEvents, + ]); info("Check the resources on available"); is( Object.keys(actualResourcesOnAvailable).length, - Object.keys(options.expectedResourcesOnAvailable).length, + Object.keys(expectedResourcesOnAvailable).length, "Got the expected number of network events fired onAvailable" ); @@ -259,8 +209,8 @@ async function testNetworkEventResources(options) { ); // assert the resources emitted when the network event is created - for (const key in options.expectedResourcesOnAvailable) { - const expected = options.expectedResourcesOnAvailable[key]; + for (const key in expectedResourcesOnAvailable) { + const expected = expectedResourcesOnAvailable[key]; const actual = actualResourcesOnAvailable[key]; assertResources(actual, expected); } @@ -269,20 +219,15 @@ async function testNetworkEventResources(options) { is( Object.keys(actualResourcesOnUpdated).length, - Object.keys(options.expectedResourcesOnUpdated).length, + Object.keys(expectedResourcesOnUpdated).length, "Got the expected number of network events fired onUpdated" ); // assert the resources emitted when the network event is updated - for (const key in options.expectedResourcesOnUpdated) { - const expected = options.expectedResourcesOnUpdated[key]; + for (const key in expectedResourcesOnUpdated) { + const expected = expectedResourcesOnUpdated[key]; const actual = actualResourcesOnUpdated[key]; assertResources(actual, expected); - is( - actual.updates.length, - expected.updates.length, - "The number of updates is correct" - ); } await resourceWatcher.unwatchResources( @@ -312,14 +257,8 @@ function assertResources(actual, expected) { expected.resourceType, "The resource type is correct" ); - is(actual.request.url, expected.request.url, "The url is correct"); - is(actual.request.method, expected.request.method, "The method is correct"); + is(actual.method, expected.method, "The method is correct"); } -const EXISTING_REQUESTS_COMMANDS = [ - `await fetch("/existing_post.html", { method: "POST" });`, -]; - -const LIVE_REQUESTS_COMMANDS = [ - `await fetch("/live_get.html", { method: "GET" });`, -]; +const cachedRequest = `await fetch("/cached_post.html", { method: "POST" });`; +const liveRequest = `await fetch("/live_get.html", { method: "GET" });`; diff --git a/devtools/shared/resources/transformers/moz.build b/devtools/shared/resources/transformers/moz.build index e4a91247f01d..2e3ffdb64d9f 100644 --- a/devtools/shared/resources/transformers/moz.build +++ b/devtools/shared/resources/transformers/moz.build @@ -5,6 +5,7 @@ DevToolsModules( "console-messages.js", "error-messages.js", + "network-events.js", "root-node.js", "storage-local-storage.js", "storage-session-storage.js", diff --git a/devtools/shared/resources/transformers/network-events.js b/devtools/shared/resources/transformers/network-events.js new file mode 100644 index 000000000000..55a0efea771d --- /dev/null +++ b/devtools/shared/resources/transformers/network-events.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + getUrlDetails, + // eslint-disable-next-line mozilla/reject-some-requires +} = require("devtools/client/netmonitor/src/utils/request-utils"); + +module.exports = function({ resource }) { + resource.urlDetails = getUrlDetails(resource.url); + resource.startedMs = Date.parse(resource.startedDateTime); + return resource; +};