diff --git a/devtools/client/netmonitor/src/components/RequestListContent.js b/devtools/client/netmonitor/src/components/RequestListContent.js index d1ad7e416f16..0d7c526f0db7 100644 --- a/devtools/client/netmonitor/src/components/RequestListContent.js +++ b/devtools/client/netmonitor/src/components/RequestListContent.js @@ -242,6 +242,7 @@ class RequestListContent extends Component { onItemMouseDown, onSecurityIconMouseDown, onWaterfallMouseDown, + requestFilterTypes, scale, selectedRequest, } = this.props; @@ -272,6 +273,7 @@ class RequestListContent extends Component { onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause), onSecurityIconMouseDown: () => onSecurityIconMouseDown(item.securityState), onWaterfallMouseDown: () => onWaterfallMouseDown(), + requestFilterTypes, })) ) ) diff --git a/devtools/client/netmonitor/src/components/RequestListItem.js b/devtools/client/netmonitor/src/components/RequestListItem.js index f10e48e3bf79..371fc4a7cd46 100644 --- a/devtools/client/netmonitor/src/components/RequestListItem.js +++ b/devtools/client/netmonitor/src/components/RequestListItem.js @@ -7,7 +7,10 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); -const { propertiesEqual } = require("../utils/request-utils"); +const { + fetchNetworkUpdatePacket, + propertiesEqual, +} = require("../utils/request-utils"); const { RESPONSE_HEADERS } = require("../constants"); // Components @@ -57,13 +60,16 @@ const UPDATED_REQ_ITEM_PROPS = [ "startedMillis", "totalTime", "requestCookies", + "requestHeaders", "responseCookies", + "responseHeaders", ]; const UPDATED_REQ_PROPS = [ "firstRequestStartedMillis", "index", "isSelected", + "requestFilterTypes", "waterfallWidth", ]; @@ -86,6 +92,7 @@ class RequestListItem extends Component { onMouseDown: PropTypes.func.isRequired, onSecurityIconMouseDown: PropTypes.func.isRequired, onWaterfallMouseDown: PropTypes.func.isRequired, + requestFilterTypes: PropTypes.string.isRequired, waterfallWidth: PropTypes.number, }; } @@ -94,6 +101,26 @@ class RequestListItem extends Component { if (this.props.isSelected) { this.refs.listItem.focus(); } + + let { connector, item, requestFilterTypes } = this.props; + // Filtering XHR & WS require to lazily fetch requestHeaders & responseHeaders + if (requestFilterTypes.get("xhr") || requestFilterTypes.get("ws")) { + fetchNetworkUpdatePacket(connector.requestData, item, [ + "requestHeaders", + "responseHeaders", + ]); + } + } + + componentWillReceiveProps(nextProps) { + let { connector, item, requestFilterTypes } = nextProps; + // Filtering XHR & WS require to lazily fetch requestHeaders & responseHeaders + if (requestFilterTypes.get("xhr") || requestFilterTypes.get("ws")) { + fetchNetworkUpdatePacket(connector.requestData, item, [ + "requestHeaders", + "responseHeaders", + ]); + } } shouldComponentUpdate(nextProps) { diff --git a/devtools/client/netmonitor/src/components/StackTracePanel.js b/devtools/client/netmonitor/src/components/StackTracePanel.js index 0955fb04169c..5110c3e29921 100644 --- a/devtools/client/netmonitor/src/components/StackTracePanel.js +++ b/devtools/client/netmonitor/src/components/StackTracePanel.js @@ -46,8 +46,6 @@ class StackTracePanel extends Component { fetchNetworkUpdatePacket(connector.requestData, request, ["stackTrace"]); } - // Rendering - render() { let { connector, diff --git a/devtools/client/netmonitor/src/components/Toolbar.js b/devtools/client/netmonitor/src/components/Toolbar.js index a352cf8c2278..eee8725f9867 100644 --- a/devtools/client/netmonitor/src/components/Toolbar.js +++ b/devtools/client/netmonitor/src/components/Toolbar.js @@ -18,9 +18,9 @@ const { getTypeFilteredRequests, isNetworkDetailsToggleButtonDisabled, } = require("../selectors/index"); - const { autocompleteProvider } = require("../utils/filter-autocomplete-provider"); const { L10N } = require("../utils/l10n"); +const { fetchNetworkUpdatePacket } = require("../utils/request-utils"); // Components const SearchBox = createFactory(require("devtools/client/shared/components/SearchBox")); @@ -135,9 +135,12 @@ class Toolbar extends Component { onSearchBoxFocus() { let { connector, filteredRequests } = this.props; - // Fetch responseCookies for building autocomplete list + // Fetch responseCookies & responseHeaders for building autocomplete list filteredRequests.forEach((request) => { - connector.requestData(request.id, "responseCookies"); + fetchNetworkUpdatePacket(connector.requestData, request, [ + "responseCookies", + "responseHeaders", + ]); }); } diff --git a/devtools/client/netmonitor/src/connector/firefox-connector.js b/devtools/client/netmonitor/src/connector/firefox-connector.js index 35fc02d8a58f..8b72bd685d03 100644 --- a/devtools/client/netmonitor/src/connector/firefox-connector.js +++ b/devtools/client/netmonitor/src/connector/firefox-connector.js @@ -71,16 +71,18 @@ class FirefoxConnector { this.removeListeners(); if (this.tabTarget) { + // Unregister `will-navigate` needs to be done before `this.timelineFront.destroy()` + // since this.tabTarget might be nullified after timelineFront.destroy(). + this.tabTarget.off("will-navigate"); // The timeline front wasn't initialized and started if the server wasn't // recent enough to emit the markers we were interested in. if (this.tabTarget.getTrait("documentLoadingMarkers") && this.timelineFront) { this.timelineFront.off("doc-loading", this.onDocLoadingMarker); await this.timelineFront.destroy(); } - - this.tabTarget.off("will-navigate"); this.tabTarget = null; } + this.webConsoleClient = null; this.timelineFront = null; this.dataProvider = null; diff --git a/devtools/client/netmonitor/src/connector/firefox-data-provider.js b/devtools/client/netmonitor/src/connector/firefox-data-provider.js index 29ac700c743b..9449c5a4c503 100644 --- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js +++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js @@ -24,8 +24,7 @@ class FirefoxDataProvider { this.actions = actions; // Internal properties - this.payloadQueue = []; - this.rdpRequestMap = new Map(); + this.payloadQueue = new Map(); // Map[key string => Promise] used by `requestData` to prevent requesting the same // request data twice. @@ -33,7 +32,6 @@ class FirefoxDataProvider { // Fetching data from the backend this.getLongString = this.getLongString.bind(this); - this.getRequestFromQueue = this.getRequestFromQueue.bind(this); // Event handlers this.onNetworkEvent = this.onNetworkEvent.bind(this); @@ -72,9 +70,8 @@ class FirefoxDataProvider { stacktrace: cause.stacktrace, fromCache, - fromServiceWorker}, - true, - ); + fromServiceWorker, + }, true); } emit(EVENTS.REQUEST_ADDED, id); @@ -88,7 +85,6 @@ class FirefoxDataProvider { */ async updateRequest(id, data) { let { - mimeType, responseContent, responseCookies, responseHeaders, @@ -106,7 +102,7 @@ class FirefoxDataProvider { requestCookiesObj, responseCookiesObj, ] = await Promise.all([ - this.fetchResponseContent(mimeType, responseContent), + this.fetchResponseContent(responseContent), this.fetchRequestHeaders(requestHeaders), this.fetchResponseHeaders(responseHeaders), this.fetchPostData(requestPostData), @@ -124,18 +120,24 @@ class FirefoxDataProvider { responseCookiesObj ); - this.pushRequestToQueue(id, payload); + if (this.actions.updateRequest) { + await this.actions.updateRequest(id, payload, true); + } return payload; } - async fetchResponseContent(mimeType, responseContent) { + async fetchResponseContent(responseContent) { let payload = {}; - if (mimeType && responseContent && responseContent.content) { + if (responseContent && responseContent.content) { let { text } = responseContent.content; let response = await this.getLongString(text); responseContent.content.text = response; payload.responseContent = responseContent; + + // Lock down responseContentAvailable once we fetch data from back-end. + // Using this as flag to prevent fetching arrived data again. + payload.responseContentAvailable = false; } return payload; } @@ -147,6 +149,10 @@ class FirefoxDataProvider { if (headers) { payload.requestHeaders = headers; } + + // Lock down requestHeadersAvailable once we fetch data from back-end. + // Using this as flag to prevent fetching arrived data again. + payload.requestHeadersAvailable = false; } return payload; } @@ -158,6 +164,10 @@ class FirefoxDataProvider { if (headers) { payload.responseHeaders = headers; } + + // Lock down responseHeadersAvailable once we fetch data from back-end. + // Using this as flag to prevent fetching arrived data again. + payload.responseHeadersAvailable = false; } return payload; } @@ -186,32 +196,6 @@ class FirefoxDataProvider { return payload; } - async fetchResponseCookies(responseCookies) { - let payload = {}; - if (responseCookies) { - let resCookies = []; - // response store cookies in responseCookies or responseCookies.cookies - let cookies = responseCookies.cookies ? - responseCookies.cookies : responseCookies; - // make sure cookies is iterable - if (typeof cookies[Symbol.iterator] === "function") { - for (let cookie of cookies) { - resCookies.push(Object.assign({}, cookie, { - value: await this.getLongString(cookie.value), - })); - } - if (resCookies.length) { - payload.responseCookies = resCookies; - } - } - - // Lock down responseCookiesAvailable once we fetch data from back-end. - // Using this as flag to prevent fetching arrived data again. - payload.responseCookiesAvailable = false; - } - return payload; - } - async fetchRequestCookies(requestCookies) { let payload = {}; if (requestCookies) { @@ -238,14 +222,30 @@ class FirefoxDataProvider { return payload; } - /** - * Access a payload item from payload queue. - * - * @param {string} id request id - * @return {boolean} return a queued payload item from queue. - */ - getRequestFromQueue(id) { - return this.payloadQueue.find((item) => item.id === id); + async fetchResponseCookies(responseCookies) { + let payload = {}; + if (responseCookies) { + let resCookies = []; + // response store cookies in responseCookies or responseCookies.cookies + let cookies = responseCookies.cookies ? + responseCookies.cookies : responseCookies; + // make sure cookies is iterable + if (typeof cookies[Symbol.iterator] === "function") { + for (let cookie of cookies) { + resCookies.push(Object.assign({}, cookie, { + value: await this.getLongString(cookie.value), + })); + } + if (resCookies.length) { + payload.responseCookies = resCookies; + } + } + + // Lock down responseCookiesAvailable once we fetch data from back-end. + // Using this as flag to prevent fetching arrived data again. + payload.responseCookiesAvailable = false; + } + return payload; } /** @@ -254,51 +254,22 @@ class FirefoxDataProvider { * @return {boolean} returns true if the payload queue is empty */ isPayloadQueueEmpty() { - return this.payloadQueue.length === 0; - } - - /** - * Return true if payload is ready (all data fetched from the backend) - * - * @param {string} id request id - * @return {boolean} return whether a specific networkEvent has been updated completely. - */ - isRequestPayloadReady(id) { - let record = this.rdpRequestMap.get(id); - if (!record) { - return false; - } - - let { payload } = this.getRequestFromQueue(id); - - // The payload is ready when all values in the record are true. - // Note that we never fetch response header/cookies for request with security issues. - // Bug 1404917 should simplify this heuristic by making all these field be lazily - // fetched, only on-demand. - return record.requestHeaders && record.eventTimings && - (record.responseHeaders || payload.securityState === "broken" || - (!payload.status && payload.responseContentAvailable)); + return this.payloadQueue.size === 0; } /** * Merge upcoming networkEventUpdate payload into existing one. * - * @param {string} id request id + * @param {string} id request actor id * @param {object} payload request data payload */ pushRequestToQueue(id, payload) { - let request = this.getRequestFromQueue(id); - if (!request) { - this.payloadQueue.push({ id, payload }); - } else { - // Merge upcoming networkEventUpdate payload into existing one - request.payload = Object.assign({}, request.payload, payload); + let payloadFromQueue = this.payloadQueue.get(id); + if (!payloadFromQueue) { + payloadFromQueue = {}; + this.payloadQueue.set(id, payloadFromQueue); } - } - - cleanUpQueue(id) { - this.payloadQueue = this.payloadQueue.filter( - request => request.id != id); + Object.assign(payloadFromQueue, payload); } /** @@ -332,7 +303,7 @@ class FirefoxDataProvider { * @param {string} type message type * @param {object} networkInfo network request information */ - onNetworkEvent(type, networkInfo) { + async onNetworkEvent(type, networkInfo) { let { actor, cause, @@ -346,14 +317,7 @@ class FirefoxDataProvider { startedDateTime, } = networkInfo; - // Create tracking record for this request. - this.rdpRequestMap.set(actor, { - requestHeaders: false, - responseHeaders: false, - eventTimings: false, - }); - - this.addRequest(actor, { + await this.addRequest(actor, { cause, fromCache, fromServiceWorker, @@ -373,116 +337,72 @@ class FirefoxDataProvider { * @param {object} packet the message received from the server. * @param {object} networkInfo the network request information. */ - onNetworkEventUpdate(type, data) { + async onNetworkEventUpdate(type, data) { let { packet, networkInfo } = data; let { actor } = networkInfo; let { updateType } = packet; - // When we pause and resume, we may receive `networkEventUpdate` for a request - // that started during the pause and we missed its `networkEvent`. - if (!this.rdpRequestMap.has(actor)) { - return; - } - switch (updateType) { - case "requestHeaders": - case "responseHeaders": - this.requestPayloadData(actor, updateType); - break; - case "requestCookies": - case "responseCookies": - case "requestPostData": - // This field helps knowing when/if updateType property is available - // and can be requested via `requestData` - this.updateRequest(actor, { [`${updateType}Available`]: true }); - break; case "securityInfo": - this.updateRequest(actor, { securityState: networkInfo.securityInfo }); + this.pushRequestToQueue(actor, { securityState: networkInfo.securityInfo }); break; case "responseStart": - this.updateRequest(actor, { + this.pushRequestToQueue(actor, { httpVersion: networkInfo.response.httpVersion, remoteAddress: networkInfo.response.remoteAddress, remotePort: networkInfo.response.remotePort, status: networkInfo.response.status, statusText: networkInfo.response.statusText, headersSize: networkInfo.response.headersSize - }).then(() => { - emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor); }); + emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor); break; case "responseContent": - this.updateRequest(actor, { + this.pushRequestToQueue(actor, { contentSize: networkInfo.response.bodySize, transferredSize: networkInfo.response.transferredSize, mimeType: networkInfo.response.content.mimeType, - // This field helps knowing when/if responseContent property is available - // and can be requested via `requestData` - responseContentAvailable: true, }); break; case "eventTimings": this.pushRequestToQueue(actor, { totalTime: networkInfo.totalTime }); - this.requestPayloadData(actor, updateType); + await this._requestData(actor, updateType); break; } + // This available field helps knowing when/if updateType property is arrived + // and can be requested via `requestData` + this.pushRequestToQueue(actor, { [`${updateType}Available`]: true }); + + this.onPayloadDataReceived(actor); + emit(EVENTS.NETWORK_EVENT_UPDATED, actor); } /** - * Wrapper method for requesting HTTP details data for the payload. - * - * It is specific to all requests done from `onNetworkEventUpdate`, for data that are - * immediately fetched whenever the data is available. - * - * All these requests are cached into `rdpRequestMap`. All requests related to a given - * actor will be collected in the same record. - * - * Once bug 1404917 is completed, we should no longer use this method. - * All request fields should be loaded only on-demand, via `requestData` method. - * - * @param {string} actor actor id (used as request id) - * @param {string} method identifier of the data we want to fetch + * Notify actions when messages from onNetworkEventUpdate are done, networkEventUpdate + * messages contain initial network info for each updateType and then we can invoke + * requestData to fetch its corresponded data lazily. + * Once all updateTypes of networkEventUpdate message are arrived, we flush merged + * request payload from pending queue and then update component. */ - requestPayloadData(actor, method) { - let record = this.rdpRequestMap.get(actor); + async onPayloadDataReceived(actor) { + let payload = this.payloadQueue.get(actor) || {}; - // If data has been already requested, do nothing. - if (record[method]) { + if (!payload.requestHeadersAvailable || !payload.requestCookiesAvailable || + !payload.eventTimingsAvailable || !payload.responseContentAvailable) { return; } - let promise = this._requestData(actor, method); - promise.then(() => { - // Once we got the data toggle the Map item to `true` in order to - // make isRequestPayloadReady return `true` once all the data is fetched. - record[method] = true; - this.onPayloadDataReceived(actor, method, !record); - }); - } + this.payloadQueue.delete(actor); - /** - * Executed when new data are received from the backend. - */ - async onPayloadDataReceived(actor, type) { - // Notify actions when all the sync request from onNetworkEventUpdate are done, - // or, everytime requestData is called for fetching data lazily. - if (this.isRequestPayloadReady(actor)) { - let payloadFromQueue = this.getRequestFromQueue(actor).payload; - - // Clean up - this.cleanUpQueue(actor); - this.rdpRequestMap.delete(actor); - - if (this.actions.updateRequest) { - await this.actions.updateRequest(actor, payloadFromQueue, true); - } - - // This event is fired only once per request, once all the properties are fetched - // from `onNetworkEventUpdate`. There should be no more RDP requests after this. - emit(EVENTS.PAYLOAD_READY, actor); + if (this.actions.updateRequest) { + await this.actions.updateRequest(actor, payload, true); } + + // This event is fired only once per request, once all the properties are fetched + // from `onNetworkEventUpdate`. There should be no more RDP requests after this. + emit(EVENTS.PAYLOAD_READY, actor); } /** @@ -508,21 +428,20 @@ class FirefoxDataProvider { return promise; } // Fetch the data - promise = this._requestData(actor, method); - this.lazyRequestData.set(key, promise); - promise.then(async () => { + promise = this._requestData(actor, method).then(async (payload) => { // Remove the request from the cache, any new call to requestData will fetch the // data again. - this.lazyRequestData.delete(key, promise); + this.lazyRequestData.delete(key); if (this.actions.updateRequest) { - await this.actions.updateRequest( - actor, - this.getRequestFromQueue(actor).payload, - true, - ); + await this.actions.updateRequest(actor, payload, true); } + + return payload; }); + + this.lazyRequestData.set(key, promise); + return promise; } @@ -557,7 +476,7 @@ class FirefoxDataProvider { // e.g. CustomRequestPanel will clone a request with additional '-clone' actor id this.webConsoleClient[clientMethodName](actor.replace("-clone", ""), (res) => { if (res.error) { - console.error(res.message); + reject(new Error(res.message)); } resolve(res); }); @@ -581,12 +500,25 @@ class FirefoxDataProvider { * * @param {object} response the message received from the server. */ - onRequestHeaders(response) { - return this.updateRequest(response.from, { + async onRequestHeaders(response) { + let payload = await this.updateRequest(response.from, { requestHeaders: response - }).then(() => { - emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from); }); + emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from); + return payload.requestHeaders; + } + + /** + * Handles additional information received for a "responseHeaders" packet. + * + * @param {object} response the message received from the server. + */ + async onResponseHeaders(response) { + let payload = await this.updateRequest(response.from, { + responseHeaders: response + }); + emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from); + return payload.responseHeaders; } /** @@ -620,25 +552,12 @@ class FirefoxDataProvider { * * @param {object} response the message received from the server. */ - onSecurityInfo(response) { - return this.updateRequest(response.from, { + async onSecurityInfo(response) { + let payload = await this.updateRequest(response.from, { securityInfo: response.securityInfo - }).then(() => { - emit(EVENTS.RECEIVED_SECURITY_INFO, response.from); - }); - } - - /** - * Handles additional information received for a "responseHeaders" packet. - * - * @param {object} response the message received from the server. - */ - onResponseHeaders(response) { - return this.updateRequest(response.from, { - responseHeaders: response - }).then(() => { - emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from); }); + emit(EVENTS.RECEIVED_SECURITY_INFO, response.from); + return payload.securityInfo; } /** @@ -676,12 +595,12 @@ class FirefoxDataProvider { * * @param {object} response the message received from the server. */ - onEventTimings(response) { - return this.updateRequest(response.from, { + async onEventTimings(response) { + let payload = await this.updateRequest(response.from, { eventTimings: response - }).then(() => { - emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from); }); + emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from); + return payload.eventTimings; } /** diff --git a/devtools/client/netmonitor/src/constants.js b/devtools/client/netmonitor/src/constants.js index d922b3f52047..ca4d29b15e9f 100644 --- a/devtools/client/netmonitor/src/constants.js +++ b/devtools/client/netmonitor/src/constants.js @@ -73,8 +73,8 @@ const EVENTS = { RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData", // When security information begins and finishes receiving. - UPDATING_SECURITY_INFO: "NetMonitor::NetworkEventUpdating:SecurityInfo", - RECEIVED_SECURITY_INFO: "NetMonitor::NetworkEventUpdated:SecurityInfo", + UPDATING_SECURITY_INFO: "NetMonitor:NetworkEventUpdating:SecurityInfo", + RECEIVED_SECURITY_INFO: "NetMonitor:NetworkEventUpdated:SecurityInfo", // When response headers begin and finish receiving. UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders", @@ -113,6 +113,7 @@ const UPDATE_PROPS = [ "httpVersion", "securityState", "securityInfo", + "securityInfoAvailable", "mimeType", "contentSize", "transferredSize", @@ -121,12 +122,14 @@ const UPDATE_PROPS = [ "headersSize", "customQueryValue", "requestHeaders", + "requestHeadersAvailable", "requestHeadersFromUploadStream", "requestCookies", "requestCookiesAvailable", "requestPostData", "requestPostDataAvailable", "responseHeaders", + "responseHeadersAvailable", "responseCookies", "responseCookiesAvailable", "responseContent", diff --git a/devtools/client/netmonitor/src/har/har-builder.js b/devtools/client/netmonitor/src/har/har-builder.js index f7b50260ff37..33ddcbff09b6 100644 --- a/devtools/client/netmonitor/src/har/har-builder.js +++ b/devtools/client/netmonitor/src/har/har-builder.js @@ -153,18 +153,31 @@ HarBuilder.prototype = { }, buildRequest: async function (file) { + // When using HarAutomation, HarCollector will automatically fetch requestHeaders + // and requestCookies, but when we use it from netmonitor, FirefoxDataProvider + // should fetch it itself lazily, via requestData. + + let requestHeaders = file.requestHeaders; + if (!requestHeaders && this._options.requestData) { + requestHeaders = await this._options.requestData(file.id, "requestHeaders"); + } + + let requestCookies = file.requestCookies; + if (!requestCookies && this._options.requestData) { + requestCookies = await this._options.requestData(file.id, "requestCookies"); + } + let request = { bodySize: 0 }; - request.method = file.method; request.url = file.url; request.httpVersion = file.httpVersion || ""; - request.headers = this.buildHeaders(file.requestHeaders); + request.headers = this.buildHeaders(requestHeaders); request.headers = this.appendHeadersPostData(request.headers, file); - request.cookies = this.buildCookies(file.requestCookies); + request.cookies = this.buildCookies(requestCookies); request.queryString = parseQueryString(getUrlQuery(file.url)) || []; - request.headersSize = file.requestHeaders.headersSize; + request.headersSize = requestHeaders.headersSize; request.postData = await this.buildPostData(file); if (request.postData && request.postData.text) { @@ -234,9 +247,9 @@ HarBuilder.prototype = { }, buildPostData: async function (file) { - // When using HarAutomation, HarCollector will automatically fetch requestPostData, - // but when we use it from netmonitor, FirefoxDataProvider should fetch it itself - // lazily, via requestData. + // When using HarAutomation, HarCollector will automatically fetch requestPostData + // and requestHeaders, but when we use it from netmonitor, FirefoxDataProvider + // should fetch it itself lazily, via requestData. let requestPostData = file.requestPostData; let requestHeaders = file.requestHeaders; let requestHeadersFromUploadStream; @@ -251,6 +264,10 @@ HarBuilder.prototype = { return undefined; } + if (!requestHeaders && this._options.requestData) { + requestHeaders = await this._options.requestData(file.id, "requestHeaders"); + } + let postData = { mimeType: findValue(requestHeaders.headers, "content-type"), params: [], @@ -288,6 +305,20 @@ HarBuilder.prototype = { }, buildResponse: async function (file) { + // When using HarAutomation, HarCollector will automatically fetch responseHeaders + // and responseCookies, but when we use it from netmonitor, FirefoxDataProvider + // should fetch it itself lazily, via requestData. + + let responseHeaders = file.responseHeaders; + if (!responseHeaders && this._options.requestData) { + responseHeaders = await this._options.requestData(file.id, "responseHeaders"); + } + + let responseCookies = file.responseCookies; + if (!responseCookies && this._options.requestData) { + responseCookies = await this._options.requestData(file.id, "responseCookies"); + } + let response = { status: 0 }; @@ -296,14 +327,11 @@ HarBuilder.prototype = { if (file.status) { response.status = parseInt(file.status, 10); } - - let responseHeaders = file.responseHeaders; - response.statusText = file.statusText || ""; response.httpVersion = file.httpVersion || ""; response.headers = this.buildHeaders(responseHeaders); - response.cookies = this.buildCookies(file.responseCookies); + response.cookies = this.buildCookies(responseCookies); response.content = await this.buildContent(file); let headers = responseHeaders ? responseHeaders.headers : null; diff --git a/devtools/client/netmonitor/src/har/test/browser.ini b/devtools/client/netmonitor/src/har/test/browser.ini index 61e3cd2d6bc4..3d225b5f5094 100644 --- a/devtools/client/netmonitor/src/har/test/browser.ini +++ b/devtools/client/netmonitor/src/har/test/browser.ini @@ -7,7 +7,6 @@ support-files = html_har_post-data-test-page.html !/devtools/client/netmonitor/test/head.js !/devtools/client/framework/test/shared-head.js - !/devtools/client/netmonitor/test/shared-head.js !/devtools/client/netmonitor/test/html_simple-test-page.html [browser_net_har_copy_all_as_har.js] diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js b/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js index 139d1025676b..b7c52dca22df 100644 --- a/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js +++ b/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js @@ -47,11 +47,13 @@ function* throttleUploadTest(actuallyThrottle) { }); // Execute one POST request on the page and wait till its done. + let onEventTimings = monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS); let wait = waitForNetworkEvents(monitor, 1); yield ContentTask.spawn(tab.linkedBrowser, { size }, function* (args) { content.wrappedJSObject.executeTest2(args.size); }); yield wait; + yield onEventTimings; // Copy HAR into the clipboard (asynchronous). let contextMenu = new RequestListContextMenu({ connector }); diff --git a/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js b/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js index 615c4b0656f2..81ab4dd1ab89 100644 --- a/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js +++ b/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js @@ -56,8 +56,8 @@ function getAutocompleteValuesForFlag(flag, request) { break; case "has-response-header": // Some requests not having responseHeaders..? - values = request.responseHeaders && - request.responseHeaders.headers.map(h => h.name); + values = request.responseHeaders ? + request.responseHeaders.headers.map(h => h.name) : []; break; case "protocol": values.push(request.httpVersion); @@ -108,10 +108,11 @@ function getLastTokenFlagValues(lastToken, requests) { values = [...new Set(values)]; return values + .filter(value => value) .filter(value => { - if (typedFlagValue) { - let lowerTyped = typedFlagValue.toLowerCase(), - lowerValue = value.toLowerCase(); + if (typedFlagValue && value) { + let lowerTyped = typedFlagValue.toLowerCase(); + let lowerValue = value.toLowerCase(); return lowerValue.includes(lowerTyped) && lowerValue !== lowerTyped; } return typeof value !== "undefined" && value !== "" && value !== "undefined"; diff --git a/devtools/client/netmonitor/src/utils/request-utils.js b/devtools/client/netmonitor/src/utils/request-utils.js index c46538423c1d..fad7f68dd11f 100644 --- a/devtools/client/netmonitor/src/utils/request-utils.js +++ b/devtools/client/netmonitor/src/utils/request-utils.js @@ -420,33 +420,40 @@ function getResponseHeader(item, header) { /** * Extracts any urlencoded form data sections from a POST request. */ -function updateFormDataSections(props) { +async function updateFormDataSections(props) { let { connector, request = {}, updateRequest, } = props; let { + id, formDataSections, requestHeaders, + requestHeadersAvailable, requestHeadersFromUploadStream, requestPostData, + requestPostDataAvailable, } = request; - if (!formDataSections && requestHeaders && - requestHeadersFromUploadStream && requestPostData) { - getFormDataSections( + if (requestHeadersAvailable && !requestHeaders) { + requestHeaders = await connector.requestData(id, "requestHeaders"); + } + + if (requestPostDataAvailable && !requestPostData) { + requestPostData = await connector.requestData(id, "requestPostData"); + } + + if (!formDataSections && requestHeaders && requestPostData && + requestHeadersFromUploadStream) { + formDataSections = await getFormDataSections( requestHeaders, requestHeadersFromUploadStream, requestPostData, connector.getLongString, - ).then((newFormDataSections) => { - updateRequest( - request.id, - { formDataSections: newFormDataSections }, - true, - ); - }); + ); + + updateRequest(request.id, { formDataSections }, true); } } @@ -455,7 +462,7 @@ function updateFormDataSections(props) { * incoming network update packets. It's used by Network and * Console panel reducers. */ -function processNetworkUpdates(request) { +function processNetworkUpdates(request = {}) { let result = {}; for (let [key, value] of Object.entries(request)) { if (UPDATE_PROPS.includes(key)) { diff --git a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js index ab70bb427400..ff8911623c3d 100644 --- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js +++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js @@ -34,9 +34,11 @@ class RequestListContextMenu { mimeType, httpVersion, requestHeaders, + requestHeadersAvailable, requestPostData, requestPostDataAvailable, responseHeaders, + responseHeadersAvailable, responseContentAvailable, url, } = selectedRequest; @@ -67,6 +69,8 @@ class RequestListContextMenu { id: "request-list-context-copy-post-data", label: L10N.getStr("netmonitor.context.copyPostData"), accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"), + // Menu item will be visible even if data hasn't arrived, so we need to check + // *Available property and then fetch data lazily once user triggers the action. visible: !!(selectedRequest && (requestPostDataAvailable || requestPostData)), click: () => this.copyPostData(id, formDataSections), }); @@ -75,7 +79,9 @@ class RequestListContextMenu { id: "request-list-context-copy-as-curl", label: L10N.getStr("netmonitor.context.copyAsCurl"), accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"), - visible: !!selectedRequest, + // Menu item will be visible even if data hasn't arrived, so we need to check + // *Available property and then fetch data lazily once user triggers the action. + visible: !!(selectedRequest && (requestHeadersAvailable || requestHeaders)), click: () => this.copyAsCurl(id, url, method, requestHeaders, httpVersion), }); @@ -88,22 +94,28 @@ class RequestListContextMenu { id: "request-list-context-copy-request-headers", label: L10N.getStr("netmonitor.context.copyRequestHeaders"), accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"), - visible: !!(selectedRequest && requestHeaders && requestHeaders.rawHeaders), - click: () => this.copyRequestHeaders(requestHeaders.rawHeaders.trim()), + // Menu item will be visible even if data hasn't arrived, so we need to check + // *Available property and then fetch data lazily once user triggers the action. + visible: !!(selectedRequest && (requestHeadersAvailable || requestHeaders)), + click: () => this.copyRequestHeaders(id, requestHeaders), }); copySubmenu.push({ id: "response-list-context-copy-response-headers", label: L10N.getStr("netmonitor.context.copyResponseHeaders"), accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"), - visible: !!(selectedRequest && responseHeaders && responseHeaders.rawHeaders), - click: () => this.copyResponseHeaders(responseHeaders.rawHeaders.trim()), + // Menu item will be visible even if data hasn't arrived, so we need to check + // *Available property and then fetch data lazily once user triggers the action. + visible: !!(selectedRequest && (responseHeadersAvailable || responseHeaders)), + click: () => this.copyResponseHeaders(id, responseHeaders), }); copySubmenu.push({ id: "request-list-context-copy-response", label: L10N.getStr("netmonitor.context.copyResponse"), accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"), + // Menu item will be visible even if data hasn't arrived, so we need to check + // *Available property and then fetch data lazily once user triggers the action. visible: !!(selectedRequest && responseContentAvailable), click: () => this.copyResponse(id), }); @@ -280,6 +292,9 @@ class RequestListContextMenu { * Copy a cURL command from the currently selected item. */ async copyAsCurl(id, url, method, requestHeaders, httpVersion) { + if (!requestHeaders) { + requestHeaders = await this.props.connector.requestData(id, "requestHeaders"); + } let { requestPostData } = await this.props.connector .requestData(id, "requestPostData"); // Create a sanitized object for the Curl command generator. @@ -296,7 +311,12 @@ class RequestListContextMenu { /** * Copy the raw request headers from the currently selected item. */ - copyRequestHeaders(rawHeaders) { + async copyRequestHeaders(id, requestHeaders) { + if (!requestHeaders) { + requestHeaders = await this.props.connector.requestData(id, "requestHeaders"); + } + let rawHeaders = requestHeaders.rawHeaders.trim(); + if (Services.appinfo.OS !== "WINNT") { rawHeaders = rawHeaders.replace(/\r/g, ""); } @@ -306,7 +326,12 @@ class RequestListContextMenu { /** * Copy the raw response headers from the currently selected item. */ - copyResponseHeaders(rawHeaders) { + async copyResponseHeaders(id, responseHeaders) { + if (!responseHeaders) { + responseHeaders = await this.props.connector.requestData(id, "responseHeaders"); + } + let rawHeaders = responseHeaders.rawHeaders.trim(); + if (Services.appinfo.OS !== "WINNT") { rawHeaders = rawHeaders.replace(/\r/g, ""); } diff --git a/devtools/client/netmonitor/test/browser.ini b/devtools/client/netmonitor/test/browser.ini index 23ba78fa19b9..81f907681682 100644 --- a/devtools/client/netmonitor/test/browser.ini +++ b/devtools/client/netmonitor/test/browser.ini @@ -4,7 +4,6 @@ subsuite = devtools support-files = dropmarker.svg head.js - shared-head.js html_cause-test-page.html html_content-type-without-cache-test-page.html html_brotli-test-page.html diff --git a/devtools/client/netmonitor/test/browser_net_cause_redirect.js b/devtools/client/netmonitor/test/browser_net_cause_redirect.js index f4ae6bb00b46..0dc2faa67467 100644 --- a/devtools/client/netmonitor/test/browser_net_cause_redirect.js +++ b/devtools/client/netmonitor/test/browser_net_cause_redirect.js @@ -33,9 +33,11 @@ add_task(function* () { // Fetch stack-trace data from the backend and wait till // all packets are received. - let requests = getSortedRequests(store.getState()); - yield Promise.all(requests.map(requestItem => - connector.requestData(requestItem.id, "stackTrace"))); + let requests = getSortedRequests(store.getState()) + .filter((req) => !req.stacktrace) + .map((req) => connector.requestData(req.id, "stackTrace")); + + yield Promise.all(requests); EXPECTED_REQUESTS.forEach(({status, hasStack}, i) => { let item = getSortedRequests(store.getState()).get(i); diff --git a/devtools/client/netmonitor/test/browser_net_copy_headers.js b/devtools/client/netmonitor/test/browser_net_copy_headers.js index 3eeaf0cdb53c..1de776f68676 100644 --- a/devtools/client/netmonitor/test/browser_net_copy_headers.js +++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js @@ -32,8 +32,6 @@ add_task(function* () { let selectedRequest = getSelectedRequest(store.getState()); is(selectedRequest, requestItem, "Proper request is selected"); - ok(selectedRequest.requestHeaders, "Selected request should have request headers"); - ok(selectedRequest.responseHeaders, "Selected request should have response headers"); const EXPECTED_REQUEST_HEADERS = [ `${method} ${SIMPLE_URL} ${httpVersion}`, diff --git a/devtools/client/netmonitor/test/browser_net_curl-utils.js b/devtools/client/netmonitor/test/browser_net_curl-utils.js index 8a7f8e993e6b..7a1c78d5cc3b 100644 --- a/devtools/client/netmonitor/test/browser_net_curl-utils.js +++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js @@ -235,7 +235,7 @@ function testEscapeStringWin() { } function* createCurlData(selected, getLongString, requestData) { - let { url, method, httpVersion } = selected; + let { id, url, method, httpVersion } = selected; // Create a sanitized object for the Curl command generator. let data = { @@ -246,13 +246,14 @@ function* createCurlData(selected, getLongString, requestData) { postDataText: null }; + let requestHeaders = yield requestData(id, "requestHeaders"); // Fetch header values. - for (let { name, value } of selected.requestHeaders.headers) { + for (let { name, value } of requestHeaders.headers) { let text = yield getLongString(value); data.headers.push({ name: name, value: text }); } - let { requestPostData } = yield requestData(selected.id, "requestPostData"); + let { requestPostData } = yield requestData(id, "requestPostData"); // Fetch the request payload. if (requestPostData) { let postData = requestPostData.postData.text; diff --git a/devtools/client/netmonitor/test/browser_net_filter-01.js b/devtools/client/netmonitor/test/browser_net_filter-01.js index 92c140092093..e96d090650c9 100644 --- a/devtools/client/netmonitor/test/browser_net_filter-01.js +++ b/devtools/client/netmonitor/test/browser_net_filter-01.js @@ -317,8 +317,15 @@ add_task(function* () { is(getSelectedIndex(store.getState()), 0, "The first item should be still selected after filtering."); - const items = getSortedRequests(store.getState()); - const visibleItems = getDisplayedRequests(store.getState()); + let items = getSortedRequests(store.getState()); + let visibleItems; + + // Filter results will be updated asynchronously, so we should wait until + // displayed requests reach final state. + yield waitUntil(() => { + visibleItems = getDisplayedRequests(store.getState()); + return visibleItems.size === visibility.filter(e => e).length; + }); is(items.size, visibility.length, "There should be a specific amount of items in the requests menu."); diff --git a/devtools/client/netmonitor/test/browser_net_filter-flags.js b/devtools/client/netmonitor/test/browser_net_filter-flags.js index 19a27d2dc0ae..546fb332d51d 100644 --- a/devtools/client/netmonitor/test/browser_net_filter-flags.js +++ b/devtools/client/netmonitor/test/browser_net_filter-flags.js @@ -145,8 +145,20 @@ add_task(function* () { store.dispatch(Actions.batchEnable(false)); + function type(string) { + for (let ch of string) { + EventUtils.synthesizeKey(ch, {}, monitor.panelWin); + } + } + + // Filtering network request will start fetching data lazily + // (fetching requestHeaders & responseHeaders for filtering WS & XHR) + // Lazy fetching will be executed when user focuses on filter box. function setFreetextFilter(value) { - store.dispatch(Actions.setRequestFilterText(value)); + let filterBox = document.querySelector(".devtools-filterinput"); + filterBox.focus(); + filterBox.value = ""; + type(value); } info("Starting test... "); @@ -350,8 +362,15 @@ add_task(function* () { yield waitUntil(() => requestsListStatus.title); } - const items = getSortedRequests(store.getState()); - const visibleItems = getDisplayedRequests(store.getState()); + let items = getSortedRequests(store.getState()); + let visibleItems = getDisplayedRequests(store.getState()); + + // Filter results will be updated asynchronously, so we should wait until + // displayed requests reach final state. + yield waitUntil(() => { + visibleItems = getDisplayedRequests(store.getState()); + return visibleItems.size === visibility.filter(e => e).length; + }); is(items.size, visibility.length, "There should be a specific amount of items in the requests menu."); @@ -362,6 +381,15 @@ add_task(function* () { let itemId = items.get(i).id; let shouldBeVisible = !!visibility[i]; let isThere = visibleItems.some(r => r.id == itemId); + + // Filter results will be updated asynchronously, so we should wait until + // displayed requests reach final state. + yield waitUntil(() => { + visibleItems = getDisplayedRequests(store.getState()); + isThere = visibleItems.some(r => r.id == itemId); + return isThere === shouldBeVisible; + }); + is(isThere, shouldBeVisible, `The item at index ${i} has visibility=${shouldBeVisible}`); diff --git a/devtools/client/netmonitor/test/browser_net_headers_sorted.js b/devtools/client/netmonitor/test/browser_net_headers_sorted.js index 081dc7ad96fe..d0d8b94bd128 100644 --- a/devtools/client/netmonitor/test/browser_net_headers_sorted.js +++ b/devtools/client/netmonitor/test/browser_net_headers_sorted.js @@ -12,12 +12,14 @@ add_task(function* () { let { document, store, windowRequire } = monitor.panelWin; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); + let { + getSortedRequests, + } = windowRequire("devtools/client/netmonitor/src/selectors/index"); store.dispatch(Actions.batchEnable(false)); - tab.linkedBrowser.reload(); - let wait = waitForNetworkEvents(monitor, 1); + tab.linkedBrowser.reload(); yield wait; wait = waitForDOM(document, ".headers-overview"); @@ -25,6 +27,11 @@ add_task(function* () { document.querySelectorAll(".request-list-item")[0]); yield wait; + yield waitUntil(() => { + let request = getSortedRequests(store.getState()).get(0); + return request.requestHeaders && request.responseHeaders; + }); + info("Check if Request-Headers and Response-Headers are sorted"); let expectedResponseHeaders = ["cache-control", "connection", "content-length", "content-type", "date", "expires", "foo-bar", diff --git a/devtools/client/netmonitor/test/browser_net_persistent_logs.js b/devtools/client/netmonitor/test/browser_net_persistent_logs.js index 73b8fd0a8c9f..4e57b83bbc24 100644 --- a/devtools/client/netmonitor/test/browser_net_persistent_logs.js +++ b/devtools/client/netmonitor/test/browser_net_persistent_logs.js @@ -12,17 +12,26 @@ add_task(function* () { let { tab, monitor } = yield initNetMonitor(SINGLE_GET_URL); info("Starting test... "); - let { document } = monitor.panelWin; + let { document, store, windowRequire } = monitor.panelWin; + let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); + + store.dispatch(Actions.batchEnable(false)); Services.prefs.setBoolPref("devtools.netmonitor.persistlog", false); yield reloadAndWait(); + // Using waitUntil in the test is necessary to ensure all requests are added correctly. + // Because reloadAndWait call may catch early uncaught requests from initNetMonitor, so + // the actual number of requests after reloadAndWait could be wrong since all requests + // haven't finished. + yield waitUntil(() => document.querySelectorAll(".request-list-item").length === 2); is(document.querySelectorAll(".request-list-item").length, 2, "The request list should have two items at this point."); yield reloadAndWait(); + yield waitUntil(() => document.querySelectorAll(".request-list-item").length === 2); // Since the reload clears the log, we still expect two requests in the log is(document.querySelectorAll(".request-list-item").length, 2, "The request list should still have two items at this point."); @@ -32,6 +41,7 @@ add_task(function* () { yield reloadAndWait(); + yield waitUntil(() => document.querySelectorAll(".request-list-item").length === 4); // Since we togged the persistence logs, we expect four items after the reload is(document.querySelectorAll(".request-list-item").length, 4, "The request list should now have four items at this point."); diff --git a/devtools/client/netmonitor/test/browser_net_raw_headers.js b/devtools/client/netmonitor/test/browser_net_raw_headers.js index 470f32328f17..d459218f3ad1 100644 --- a/devtools/client/netmonitor/test/browser_net_raw_headers.js +++ b/devtools/client/netmonitor/test/browser_net_raw_headers.js @@ -25,7 +25,7 @@ add_task(function* () { }); yield wait; - wait = waitForDOM(document, ".headers-overview"); + wait = waitForDOM(document, "#headers-panel .tree-section", 2); EventUtils.sendMouseEvent({ type: "mousedown" }, document.querySelectorAll(".request-list-item")[0]); yield wait; diff --git a/devtools/client/netmonitor/test/browser_net_resend.js b/devtools/client/netmonitor/test/browser_net_resend.js index 3b77c3ddcdbc..b400edde35c9 100644 --- a/devtools/client/netmonitor/test/browser_net_resend.js +++ b/devtools/client/netmonitor/test/browser_net_resend.js @@ -17,7 +17,6 @@ add_task(function* () { info("Starting test... "); let { document, store, windowRequire, connector } = monitor.panelWin; - let { requestData } = connector; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); let { getSelectedRequest, @@ -56,7 +55,15 @@ add_task(function* () { store.dispatch(Actions.sendCustomRequest(connector)); yield wait; - let sentItem = getSelectedRequest(store.getState()); + let sentItem; + // Testing sent request will require updated requestHeaders and requestPostData, + // we must wait for both properties get updated before starting test. + yield waitUntil(() => { + sentItem = getSelectedRequest(store.getState()); + origItem = getSortedRequests(store.getState()).get(0); + return sentItem.requestHeaders && sentItem.requestPostData && + origItem.requestHeaders && origItem.requestPostData; + }); yield testSentRequest(sentItem, origItem); @@ -164,13 +171,8 @@ add_task(function* () { let hasUAHeader = headers.some(h => `${h.name}: ${h.value}` == ADD_UA_HEADER); ok(hasUAHeader, "User-Agent header added to sent request"); - let { requestPostData: clonedRequestPostData } = yield requestData(data.id, - "requestPostData"); - let { requestPostData: origRequestPostData } = yield requestData(origData.id, - "requestPostData"); - - is(clonedRequestPostData.postData.text, - origRequestPostData.postData.text + ADD_POSTDATA, + is(data.requestPostData.postData.text, + origData.requestPostData.postData.text + ADD_POSTDATA, "post data added to sent request"); } diff --git a/devtools/client/netmonitor/test/browser_net_resend_cors.js b/devtools/client/netmonitor/test/browser_net_resend_cors.js index fe9b272caaec..25e7c69b6ff2 100644 --- a/devtools/client/netmonitor/test/browser_net_resend_cors.js +++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js @@ -15,6 +15,7 @@ add_task(function* () { let { store, windowRequire, connector } = monitor.panelWin; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); let { + getRequestById, getSortedRequests, } = windowRequire("devtools/client/netmonitor/src/selectors/index"); @@ -41,16 +42,27 @@ add_task(function* () { // Resend both requests without modification. Wait for resent OPTIONS, then POST. // POST is supposed to have no preflight OPTIONS request this time (CORS is disabled) let onRequests = waitForNetworkEvents(monitor, 1); - ITEMS.forEach((item) => { + for (let item of ITEMS) { info(`Selecting the ${item.method} request`); store.dispatch(Actions.selectRequest(item.id)); + // Wait for requestHeaders and responseHeaders are required when fetching data + // from back-end. + yield waitUntil(() => { + item = getRequestById(store.getState(), item.id); + return item.requestHeaders && item.responseHeaders; + }); + + let { size } = getSortedRequests(store.getState()); + info("Cloning the selected request into a custom clone"); store.dispatch(Actions.cloneSelectedRequest()); info("Sending the cloned request (without change)"); store.dispatch(Actions.sendCustomRequest(connector)); - }); + + yield waitUntil(() => getSortedRequests(store.getState()).size === size + 1); + } info("Waiting for both resent requests"); yield onRequests; @@ -63,14 +75,26 @@ add_task(function* () { is(item.status, 200, `The ${item.method} response has the right status`); if (item.method === "POST") { - // Force fetching lazy load data - let responseContent = yield connector.requestData(item.id, "responseContent"); - let { requestPostData } = yield connector.requestData(item.id, "requestPostData"); + is(item.method, "POST", `The ${item.method} request has the right method`); - is(requestPostData.postData.text, "post-data", + // Trigger responseContent update requires to wait until + // responseContentAvailable set true + yield waitUntil(() => { + item = getRequestById(store.getState(), item.id); + return item.responseContentAvailable; + }); + yield connector.requestData(item.id, "responseContent"); + + // Wait for both requestPostData & responseContent payloads arrived. + yield waitUntil(() => { + item = getRequestById(store.getState(), item.id); + return item.responseContent && item.requestPostData; + }); + + is(item.requestPostData.postData.text, "post-data", "The POST request has the right POST data"); // eslint-disable-next-line mozilla/no-cpows-in-tests - is(responseContent.content.text, "Access-Control-Allow-Origin: *", + is(item.responseContent.content.text, "Access-Control-Allow-Origin: *", "The POST response has the right content"); } } diff --git a/devtools/client/netmonitor/test/browser_net_resend_headers.js b/devtools/client/netmonitor/test/browser_net_resend_headers.js index be39b978e7c1..bc77bf86091c 100644 --- a/devtools/client/netmonitor/test/browser_net_resend_headers.js +++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js @@ -13,7 +13,7 @@ add_task(function* () { let { store, windowRequire, connector } = monitor.panelWin; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); - let { sendHTTPRequest } = connector; + let { requestData, sendHTTPRequest } = connector; let { getSortedRequests, } = windowRequire("devtools/client/netmonitor/src/selectors/index"); @@ -40,6 +40,19 @@ add_task(function* () { yield wait; let item = getSortedRequests(store.getState()).get(0); + + ok(item.requestHeadersAvailable, "headers are available for lazily fetching"); + + if (item.requestHeadersAvailable && !item.requestHeaders) { + requestData(item.id, "requestHeaders"); + } + + // Wait until requestHeaders packet gets updated. + yield waitUntil(() => { + item = getSortedRequests(store.getState()).get(0); + return item.requestHeaders; + }); + is(item.method, "POST", "The request has the right method"); is(item.url, requestUrl, "The request has the right URL"); diff --git a/devtools/client/netmonitor/test/browser_net_security-error.js b/devtools/client/netmonitor/test/browser_net_security-error.js index 9225b7e3e31f..52cbc05dcb9a 100644 --- a/devtools/client/netmonitor/test/browser_net_security-error.js +++ b/devtools/client/netmonitor/test/browser_net_security-error.js @@ -11,13 +11,12 @@ add_task(function* () { let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL); let { document, store, windowRequire } = monitor.panelWin; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); - let { EVENTS } = windowRequire("devtools/client/netmonitor/src/constants"); store.dispatch(Actions.batchEnable(false)); info("Requesting a resource that has a certificate problem."); - let requestsDone = waitForSecurityBrokenNetworkEvent(); + let requestsDone = waitForNetworkEvents(monitor, 1); yield ContentTask.spawn(tab.linkedBrowser, {}, function* () { content.wrappedJSObject.performRequests(1, "https://nocert.example.com"); }); @@ -36,25 +35,4 @@ add_task(function* () { isnot(errormsg.textContent, "", "Error message is not empty."); return teardown(monitor); - - /** - * Returns a promise that's resolved once a request with security issues is - * completed. - */ - function waitForSecurityBrokenNetworkEvent() { - let awaitedEvents = [ - "UPDATING_REQUEST_HEADERS", - "RECEIVED_REQUEST_HEADERS", - "UPDATING_REQUEST_COOKIES", - "RECEIVED_REQUEST_COOKIES", - "UPDATING_EVENT_TIMINGS", - "RECEIVED_EVENT_TIMINGS", - ]; - - let promises = awaitedEvents.map((event) => { - return monitor.panelWin.once(EVENTS[event]); - }); - - return Promise.all(promises); - } }); diff --git a/devtools/client/netmonitor/test/browser_net_security-state.js b/devtools/client/netmonitor/test/browser_net_security-state.js index 06d9ddcd0789..9af9825f845c 100644 --- a/devtools/client/netmonitor/test/browser_net_security-state.js +++ b/devtools/client/netmonitor/test/browser_net_security-state.js @@ -56,10 +56,7 @@ add_task(function* () { }); } - // waitForNetworkEvents does not work for requests with security errors as - // those only emit 9/13 events of a successful request. - let done = waitForSecurityBrokenNetworkEvent(); - + let done = waitForNetworkEvents(monitor, 1); info("Requesting a resource that has a certificate problem."); yield executeRequests(1, "https://nocert.example.com"); @@ -80,7 +77,7 @@ add_task(function* () { yield executeRequests(1, "https://example.com" + CORS_SJS_PATH); yield done; - done = waitForSecurityBrokenNetworkEvent(); + done = waitForNetworkEvents(monitor, 1); info("Requesting a resource over HTTP to localhost."); yield executeRequests(1, "http://localhost" + CORS_SJS_PATH); yield done; @@ -90,25 +87,4 @@ add_task(function* () { expectedCount, expectedCount + " events logged."); } - - /** - * Returns a promise that's resolved once a request with security issues is - * completed. - */ - function waitForSecurityBrokenNetworkEvent() { - let awaitedEvents = [ - "UPDATING_REQUEST_HEADERS", - "RECEIVED_REQUEST_HEADERS", - "UPDATING_REQUEST_COOKIES", - "RECEIVED_REQUEST_COOKIES", - "UPDATING_EVENT_TIMINGS", - "RECEIVED_EVENT_TIMINGS", - ]; - - let promises = awaitedEvents.map((event) => { - return monitor.panelWin.once(EVENTS[event]); - }); - - return Promise.all(promises); - } }); diff --git a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js index c68c6ead4b26..56defc539a01 100644 --- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js +++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js @@ -101,10 +101,6 @@ add_task(function* () { */ function waitForSecurityBrokenNetworkEvent() { let awaitedEvents = [ - "UPDATING_REQUEST_HEADERS", - "RECEIVED_REQUEST_HEADERS", - "UPDATING_REQUEST_COOKIES", - "RECEIVED_REQUEST_COOKIES", "UPDATING_EVENT_TIMINGS", "RECEIVED_EVENT_TIMINGS", ]; diff --git a/devtools/client/netmonitor/test/browser_net_simple-request-data.js b/devtools/client/netmonitor/test/browser_net_simple-request-data.js index 53e8b9288611..9ecdbae137f9 100644 --- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js +++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js @@ -27,7 +27,7 @@ function test() { initNetMonitor(SIMPLE_SJS).then(async ({ tab, monitor }) => { info("Starting test... "); - let { document, store, windowRequire } = monitor.panelWin; + let { document, store, windowRequire, connector } = monitor.panelWin; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); let { EVENTS } = windowRequire("devtools/client/netmonitor/src/constants"); let { @@ -347,7 +347,18 @@ function test() { ); }); + let wait = waitForNetworkEvents(monitor, 1); tab.linkedBrowser.reload(); + await wait; + + let requestItem = getSortedRequests(store.getState()).get(0); + + if (!requestItem.requestHeaders) { + connector.requestData(requestItem.id, "requestHeaders"); + } + if (!requestItem.responseHeaders) { + connector.requestData(requestItem.id, "responseHeaders"); + } await Promise.all(promiseList); await teardown(monitor); diff --git a/devtools/client/netmonitor/test/browser_net_status-codes.js b/devtools/client/netmonitor/test/browser_net_status-codes.js index 12b5ae10a821..37848ace5c11 100644 --- a/devtools/client/netmonitor/test/browser_net_status-codes.js +++ b/devtools/client/netmonitor/test/browser_net_status-codes.js @@ -172,6 +172,9 @@ add_task(function* () { EventUtils.sendMouseEvent({ type: "mousedown" }, document.querySelectorAll(".request-list-item")[index]); + yield waitUntil(() => document.querySelector( + "#headers-panel .tabpanel-summary-value.textbox-input")); + let panel = document.querySelector("#headers-panel"); let summaryValues = panel.querySelectorAll(".tabpanel-summary-value.textbox-input"); let { method, correctUri, details: { status, statusText } } = data; diff --git a/devtools/client/netmonitor/test/browser_net_timing-division.js b/devtools/client/netmonitor/test/browser_net_timing-division.js index 1e50e6eab4f6..953d7ff96c48 100644 --- a/devtools/client/netmonitor/test/browser_net_timing-division.js +++ b/devtools/client/netmonitor/test/browser_net_timing-division.js @@ -13,10 +13,6 @@ add_task(function* () { let { document, store, windowRequire } = monitor.panelWin; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); - let { - getSortedRequests, - } = windowRequire("devtools/client/netmonitor/src/selectors/index"); - store.dispatch(Actions.batchEnable(false)); let wait = waitForNetworkEvents(monitor, 2); @@ -44,14 +40,6 @@ add_task(function* () { is(store.getState().requests.requests.size, 2, "There should be only two requests made."); - let firstRequest = getSortedRequests(store.getState()).get(0); - let lastRequest = getSortedRequests(store.getState()).get(1); - - info("First request happened at: " + - firstRequest.responseHeaders.headers.find(e => e.name == "date").value); - info("Last request happened at: " + - lastRequest.responseHeaders.headers.find(e => e.name == "date").value); - ok(secDivs.length, "There should be at least one division on the seconds time scale."); ok(secDivs[0].textContent.match(/\d+\.\d{2}\s\w+/), diff --git a/devtools/client/netmonitor/test/browser_net_waterfall-click.js b/devtools/client/netmonitor/test/browser_net_waterfall-click.js index a518af981155..442ac3bea816 100644 --- a/devtools/client/netmonitor/test/browser_net_waterfall-click.js +++ b/devtools/client/netmonitor/test/browser_net_waterfall-click.js @@ -11,13 +11,17 @@ add_task(function* () { let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL); let { document } = monitor.panelWin; - yield performRequestsAndWait(); - - let wait = waitForDOM(document, "#timings-panel"); - let timing = document.querySelectorAll(".requests-list-timings")[0]; + let onAllEvents = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS); + yield ContentTask.spawn(tab.linkedBrowser, {}, function* () { + content.wrappedJSObject.performRequests(); + }); + yield onAllEvents; info("Clicking waterfall and waiting for panel update."); - EventUtils.synthesizeMouseAtCenter(timing, {}, monitor.panelWin); + let wait = waitForDOM(document, "#timings-panel"); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.querySelectorAll(".requests-list-timings")[0]); yield wait; @@ -25,12 +29,4 @@ add_task(function* () { "Timings tab is selected."); return teardown(monitor); - - function* performRequestsAndWait() { - let onAllEvents = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS); - yield ContentTask.spawn(tab.linkedBrowser, {}, function* () { - content.wrappedJSObject.performRequests(); - }); - yield onAllEvents; - } }); diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js index 02818a393c02..a83f8f781bfa 100644 --- a/devtools/client/netmonitor/test/head.js +++ b/devtools/client/netmonitor/test/head.js @@ -2,7 +2,6 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ /* import-globals-from ../../framework/test/shared-head.js */ -/* import-globals-from shared-head.js */ /* exported Toolbox, restartNetMonitor, teardown, waitForExplicitFinish, verifyRequestItemTarget, waitFor, testFilterButtons, loadCommonFrameScript, performRequestsInContent, waitForNetworkEvents, selectIndexAndWaitForSourceEditor */ @@ -14,10 +13,6 @@ Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); -Services.scriptloader.loadSubScript( - "chrome://mochitests/content/browser/devtools/client/netmonitor/test/shared-head.js", - this); - const { getFormattedIPAndPort, getFormattedTime, @@ -30,6 +25,7 @@ const { getUrlQuery, getUrlScheme, } = require("devtools/client/netmonitor/src/utils/request-utils"); +const { EVENTS } = require("devtools/client/netmonitor/src/constants"); /* eslint-disable no-unused-vars, max-len */ const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/"; @@ -225,23 +221,35 @@ let finishedQueue = {}; let updatingTypes = [ "NetMonitor:NetworkEventUpdating:RequestCookies", "NetMonitor:NetworkEventUpdating:ResponseCookies", + "NetMonitor:NetworkEventUpdating:RequestHeaders", + "NetMonitor:NetworkEventUpdating:ResponseHeaders", + "NetMonitor:NetworkEventUpdating:RequestPostData", + "NetMonitor:NetworkEventUpdating:ResponseContent", + "NetMonitor:NetworkEventUpdating:SecurityInfo", + "NetMonitor:NetworkEventUpdating:EventTimings", ]; let updatedTypes = [ "NetMonitor:NetworkEventUpdated:RequestCookies", "NetMonitor:NetworkEventUpdated:ResponseCookies", + "NetMonitor:NetworkEventUpdated:RequestHeaders", + "NetMonitor:NetworkEventUpdated:ResponseHeaders", + "NetMonitor:NetworkEventUpdated:RequestPostData", + "NetMonitor:NetworkEventUpdated:ResponseContent", + "NetMonitor:NetworkEventUpdated:SecurityInfo", + "NetMonitor:NetworkEventUpdated:EventTimings", ]; // Start collecting all networkEventUpdate event when panel is opened. // removeTab() should be called once all corresponded RECEIVED_* events finished. function startNetworkEventUpdateObserver(panelWin) { updatingTypes.forEach((type) => panelWin.on(type, (event, actor) => { - let key = actor + "-" + event.replace("NetMonitor:NetworkEventUpdating:", ""); + let key = actor + "-" + updatedTypes[updatingTypes.indexOf(event)]; finishedQueue[key] = finishedQueue[key] ? finishedQueue[key] + 1 : 1; })); updatedTypes.forEach((type) => panelWin.on(type, (event, actor) => { - let key = actor + "-" + event.replace("NetMonitor:NetworkEventUpdated:", ""); - finishedQueue[key]--; + let key = actor + "-" + event; + finishedQueue[key] = finishedQueue[key] ? finishedQueue[key] - 1 : -1; })); } @@ -329,10 +337,6 @@ function teardown(monitor) { return Task.spawn(function* () { let tab = monitor.toolbox.target.tab; - // Ensure that there is no pending RDP requests related to payload request - // done from FirefoxDataProvider. - info("Wait for completion of all pending RDP requests..."); - yield waitForExistingRequests(monitor); yield waitForAllNetworkUpdateEvents(); info("All pending requests finished."); @@ -346,44 +350,17 @@ function waitForNetworkEvents(monitor, getRequests) { return new Promise((resolve) => { let panel = monitor.panelWin; let { getNetworkRequest } = panel.connector; - let progress = {}; - let genericEvents = 0; + let networkEvent = 0; let payloadReady = 0; - let awaitedEventsToListeners = [ - ["UPDATING_REQUEST_HEADERS", onGenericEvent], - ["RECEIVED_REQUEST_HEADERS", onGenericEvent], - ["UPDATING_RESPONSE_HEADERS", onGenericEvent], - ["RECEIVED_RESPONSE_HEADERS", onGenericEvent], - ["UPDATING_EVENT_TIMINGS", onGenericEvent], - ["RECEIVED_EVENT_TIMINGS", onGenericEvent], - ["PAYLOAD_READY", onPayloadReady] - ]; - let expectedGenericEvents = awaitedEventsToListeners - .filter(([, listener]) => listener == onGenericEvent).length; - function initProgressForURL(url) { - if (progress[url]) { - return; - } - progress[url] = {}; - awaitedEventsToListeners.forEach(function ([e]) { - progress[url][e] = 0; - }); - } - - function updateProgressForURL(url, event) { - initProgressForURL(url); - progress[url][Object.keys(EVENTS).find(e => EVENTS[e] == event)] = 1; - } - - function onGenericEvent(event, actor) { + function onNetworkEvent(event, actor) { let networkInfo = getNetworkRequest(actor); if (!networkInfo) { // Must have been related to reloading document to disable cache. // Ignore the event. return; } - genericEvents++; + networkEvent++; maybeResolve(event, actor, networkInfo); } @@ -394,38 +371,30 @@ function waitForNetworkEvents(monitor, getRequests) { // Ignore the event. return; } - payloadReady++; maybeResolve(event, actor, networkInfo); } function maybeResolve(event, actor, networkInfo) { info("> Network event progress: " + - "Payload: " + payloadReady + "/" + getRequests + ", " + - "Generic: " + genericEvents + "/" + (getRequests * expectedGenericEvents) + ", " + + "NetworkEvent: " + networkEvent + "/" + getRequests + ", " + + "PayloadReady: " + payloadReady + "/" + getRequests + ", " + "got " + event + " for " + actor); - let url = networkInfo.request.url; - updateProgressForURL(url, event); - - // Uncomment this to get a detailed progress logging (when debugging a test) - // info("> Current state: " + JSON.stringify(progress, null, 2)); - - // There are `expectedGenericEvents` updates which need to be fired for a request - // to be considered finished. The "requestPostData" packet isn't fired for non-POST - // requests. - if (payloadReady >= getRequests && - genericEvents >= getRequests * expectedGenericEvents) { - awaitedEventsToListeners.forEach(([e, l]) => panel.off(EVENTS[e], l)); + // Wait until networkEvent & payloadReady finish for each request. + if (networkEvent >= getRequests && payloadReady >= getRequests) { + panel.off(EVENTS.NETWORK_EVENT, onNetworkEvent); + panel.off(EVENTS.PAYLOAD_READY, onPayloadReady); executeSoon(resolve); } } - awaitedEventsToListeners.forEach(([e, l]) => panel.on(EVENTS[e], l)); + panel.on(EVENTS.NETWORK_EVENT, onNetworkEvent); + panel.on(EVENTS.PAYLOAD_READY, onPayloadReady); }); } -function verifyRequestItemTarget(document, requestList, requestItem, method, +function* verifyRequestItemTarget(document, requestList, requestItem, method, url, data = {}) { info("> Verifying: " + method + " " + url + " " + data.toSource()); diff --git a/devtools/client/netmonitor/test/shared-head.js b/devtools/client/netmonitor/test/shared-head.js deleted file mode 100644 index 4d6834ff4874..000000000000 --- a/devtools/client/netmonitor/test/shared-head.js +++ /dev/null @@ -1,40 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -/* exported EVENTS, waitForExistingRequests */ - -"use strict"; - -const { EVENTS } = require("devtools/client/netmonitor/src/constants"); - -async function waitForExistingRequests(monitor) { - let { store } = monitor.panelWin; - function getRequests() { - return store.getState().requests.requests; - } - function areAllRequestsFullyLoaded() { - let requests = getRequests().valueSeq(); - for (let request of requests) { - // Ignore cloned request as we don't lazily fetch data for them - // and have arbitrary number of field set. - if (request.id.includes("-clone")) { - continue; - } - // Do same check than FirefoxDataProvider.isRequestPayloadReady, - // in order to ensure there is no more pending payload requests to be done. - if (!request.requestHeaders || !request.eventTimings || - (!request.responseHeaders && request.securityState !== "broken" && - (!request.responseContentAvailable || request.status))) { - return false; - } - } - return true; - } - // If there is no request, we are good to go. - if (getRequests().size == 0) { - return; - } - while (!areAllRequestsFullyLoaded()) { - await monitor.panelWin.once(EVENTS.PAYLOAD_READY); - } -} diff --git a/devtools/client/styleeditor/test/browser.ini b/devtools/client/styleeditor/test/browser.ini index 99cb3c724817..15b04a01a86a 100644 --- a/devtools/client/styleeditor/test/browser.ini +++ b/devtools/client/styleeditor/test/browser.ini @@ -61,7 +61,6 @@ support-files = !/devtools/client/inspector/shared/test/head.js !/devtools/client/inspector/test/head.js !/devtools/client/inspector/test/shared-head.js - !/devtools/client/netmonitor/test/shared-head.js !/devtools/client/responsive.html/test/browser/devices.json !/devtools/client/shared/test/test-actor-registry.js !/devtools/client/shared/test/test-actor.js diff --git a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js index f0ca0bbe0a94..f4d0cd29d014 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js @@ -3,14 +3,9 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -/* import-globals-from ../../netmonitor/test/shared-head.js */ - // A test to ensure Style Editor doesn't bybass cache when loading style sheet // contents (bug 978688). -Services.scriptloader.loadSubScript( - "chrome://mochitests/content/browser/devtools/client/netmonitor/test/shared-head.js", this); - const TEST_URL = TEST_BASE_HTTP + "doc_uncached.html"; add_task(function* () { @@ -39,8 +34,6 @@ add_task(function* () { info("Waiting for the source to be loaded."); yield styleeditor.UI.editors[0].getSourceEditor(); - yield waitForExistingRequests(monitor); - info("Checking Netmonitor contents."); let items = []; for (let item of getSortedRequests(store.getState())) { diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini index dd744f7a579e..eb6b20aab4fb 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini @@ -165,7 +165,6 @@ support-files = !/devtools/client/netmonitor/test/sjs_cors-test-server.sjs !/image/test/mochitest/blue.png !/devtools/client/framework/test/shared-head.js - !/devtools/client/netmonitor/test/shared-head.js [browser_console.js] skip-if = true # Bug 1406060 [browser_console_addonsdk_loader_exception.js] diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_netmonitor_shows_reqs_in_webconsole.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_netmonitor_shows_reqs_in_webconsole.js index 19d0a69bbfe5..e5358bda51e4 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_netmonitor_shows_reqs_in_webconsole.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_netmonitor_shows_reqs_in_webconsole.js @@ -64,6 +64,4 @@ async function testNetmonitor(toolbox) { let item = getSortedRequests(store.getState()).get(0); is(item.method, "GET", "The attached method is correct."); is(item.url, TEST_PATH, "The attached url is correct."); - - await waitForExistingRequests(monitor); } diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_attach.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_attach.js index 7047013d0bb2..ddd8818617ec 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_attach.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_attach.js @@ -47,10 +47,8 @@ add_task(async function task() { await consoleReady; info("network-request-payload-ready received"); - await testNetworkMessage(messageNode); - - await waitForExistingRequests(monitor); + await waitForLazyRequests(toolbox); }); async function testNetworkMessage(messageNode) { @@ -59,7 +57,24 @@ async function testNetworkMessage(messageNode) { ok(headersTab, "Headers tab is available"); // Headers tab should be selected by default, so just check its content. - let headersContent = messageNode.querySelector( - "#headers-panel .headers-overview"); + let headersContent; + await waitUntil(() => { + headersContent = messageNode.querySelector( + "#headers-panel .headers-overview"); + return headersContent; + }); + ok(headersContent, "Headers content is available"); } + +/** + * Wait until all lazily fetch requests in netmonitor get finsished. + * Otherwise test will be shutdown too early and cause failure. + */ +async function waitForLazyRequests(toolbox) { + let { ui } = toolbox.getCurrentPanel().hud; + let proxy = ui.jsterm.hud.proxy; + return waitUntil(() => { + return !proxy.networkDataProvider.lazyRequestData.size; + }); +} diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_expand.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_expand.js index 6a4301ff0307..2a4b8b01ea91 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_expand.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_expand.js @@ -247,6 +247,10 @@ async function testTimings(messageNode) { // Select Timings tab and check the content. timingsTab.click(); + await waitUntil(() => { + return !!messageNode.querySelector( + "#timings-panel .timings-container .timings-label"); + }); let timingsContent = messageNode.querySelector( "#timings-panel .timings-container .timings-label"); ok(timingsContent, "Timings content is available"); @@ -299,6 +303,10 @@ async function waitForRequestUpdates(toolbox) { }); } +/** + * Wait until all lazily fetch requests in netmonitor get finsished. + * Otherwise test will be shutdown too early and cause failure. + */ async function waitForLazyRequests(toolbox) { let {ui} = toolbox.getCurrentPanel().hud; let proxy = ui.jsterm.hud.proxy; diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_openinnet.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_openinnet.js index da945c01c068..f32063bb1995 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_openinnet.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_messages_openinnet.js @@ -75,7 +75,4 @@ async function testNetmonitorLink(toolbox, hud, url) { }); ok(true, "The attached url is correct."); - - let monitor = toolbox.getCurrentPanel(); - await waitForExistingRequests(monitor); } diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/head.js b/devtools/client/webconsole/new-console-output/test/mochitest/head.js index 29261254984a..51aa2a2ba01e 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js @@ -3,7 +3,6 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ /* import-globals-from ../../../../framework/test/shared-head.js */ -/* import-globals-from ../../../../netmonitor/test/shared-head.js */ /* eslint no-unused-vars: [2, {"vars": "local"}] */ "use strict"; @@ -14,9 +13,6 @@ Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); -Services.scriptloader.loadSubScript( - "chrome://mochitests/content/browser/devtools/client/netmonitor/test/shared-head.js", this); - var {HUDService} = require("devtools/client/webconsole/hudservice"); var WCUL10n = require("devtools/client/webconsole/webconsole-l10n"); const DOCS_GA_PARAMS = "?utm_source=mozilla" + diff --git a/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js b/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js index 13d5a5c4d1e7..ab92364ba5e9 100644 --- a/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js +++ b/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js @@ -5,9 +5,6 @@ "use strict"; -Services.scriptloader.loadSubScript( - "chrome://mochitests/content/browser/devtools/client/netmonitor/test/shared-head.js", this); - const TEST_URI = "data:text/html;charset=utf8,Test that the netmonitor " + "displays requests that have been recorded in the " + "web console, even if the netmonitor hadn't opened yet."; @@ -68,17 +65,17 @@ function* testNetmonitor(toolbox) { let { store, windowRequire } = monitor.panelWin; let Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); - let { getSortedRequests } = windowRequire("devtools/client/netmonitor/src/selectors/index"); + let { getSortedRequests } = + windowRequire("devtools/client/netmonitor/src/selectors/index"); store.dispatch(Actions.batchEnable(false)); yield waitUntil(() => store.getState().requests.requests.size > 0); - is(store.getState().requests.requests.size, 1, "Network request appears in the network panel"); + is(store.getState().requests.requests.size, 1, + "Network request appears in the network panel"); let item = getSortedRequests(store.getState()).get(0); is(item.method, "GET", "The attached method is correct."); is(item.url, TEST_PATH, "The attached url is correct."); - - yield waitForExistingRequests(monitor); } diff --git a/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js b/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js index f887784c5583..cf9465859ce0 100644 --- a/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js +++ b/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js @@ -2,8 +2,6 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -/* import-globals-from ../../netmonitor/test/shared-head.js */ - // Tests that network log messages bring up the network panel. "use strict"; @@ -12,9 +10,6 @@ const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/devtools/client/webconsole/test/" + "test-network-request.html"; -Services.scriptloader.loadSubScript( - "chrome://mochitests/content/browser/devtools/client/netmonitor/test/shared-head.js", this); - add_task(function* () { let finishedRequest = waitForFinishedRequest(({ request }) => { return request.url.endsWith("test-network-request.html"); @@ -29,13 +24,12 @@ add_task(function* () { let monitor = toolbox.getCurrentPanel(); let { store, windowRequire } = monitor.panelWin; - let { getSelectedRequest } = windowRequire("devtools/client/netmonitor/src/selectors/index"); + let { getSelectedRequest } = + windowRequire("devtools/client/netmonitor/src/selectors/index"); let selected = getSelectedRequest(store.getState()); is(selected.method, request.request.method, "The correct request is selected"); is(selected.url, request.request.url, "The correct request is definitely selected"); - - yield waitForExistingRequests(monitor); }); diff --git a/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js b/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js index 1a09312ba92d..53accecd1332 100644 --- a/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js +++ b/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js @@ -3,8 +3,6 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -/* import-globals-from ../../netmonitor/test/shared-head.js */ - // Tests that network log messages bring up the network panel and select the // right request even if it was previously filtered off. @@ -15,9 +13,6 @@ const TEST_FILE_URI = "test-network.html"; const TEST_URI = "data:text/html;charset=utf8,

test file URI"; -Services.scriptloader.loadSubScript( - "chrome://mochitests/content/browser/devtools/client/netmonitor/test/shared-head.js", this); - var hud; add_task(function* () { @@ -69,8 +64,6 @@ add_task(function* () { // All tests are done. Shutdown. HUDService.lastFinishedRequest.callback = null; htmlRequest = browser = requests = hud = null; - - yield waitForExistingRequests(monitor); }); function testMessages() {