Bug 1418927 - requestHeaders and responseHeaders should be loaded lazily r=Honza,ochameau

MozReview-Commit-ID: 5ADM5AYdJNI

--HG--
extra : rebase_source : 05c1506489ef5af67cd9fd3911e2b3c3e4659e05
This commit is contained in:
Ricky Chien 2017-12-01 18:40:36 +08:00
parent 36f8852817
commit cf0c6d5220
45 changed files with 474 additions and 501 deletions

View File

@ -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,
}))
)
)

View File

@ -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) {

View File

@ -46,8 +46,6 @@ class StackTracePanel extends Component {
fetchNetworkUpdatePacket(connector.requestData, request, ["stackTrace"]);
}
// Rendering
render() {
let {
connector,

View File

@ -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",
]);
});
}

View File

@ -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;

View File

@ -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;
}
/**

View File

@ -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",

View File

@ -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;

View File

@ -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]

View File

@ -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 });

View File

@ -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";

View File

@ -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)) {

View File

@ -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, "");
}

View File

@ -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

View File

@ -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);

View File

@ -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}`,

View File

@ -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;

View File

@ -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.");

View File

@ -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}`);

View File

@ -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",

View File

@ -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.");

View File

@ -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;

View File

@ -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");
}

View File

@ -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");
}
}

View File

@ -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");

View File

@ -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);
}
});

View File

@ -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);
}
});

View File

@ -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",
];

View File

@ -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);

View File

@ -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;

View File

@ -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+/),

View File

@ -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;
}
});

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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

View File

@ -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())) {

View File

@ -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]

View File

@ -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);
}

View File

@ -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;
});
}

View File

@ -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;

View File

@ -75,7 +75,4 @@ async function testNetmonitorLink(toolbox, hud, url) {
});
ok(true, "The attached url is correct.");
let monitor = toolbox.getCurrentPanel();
await waitForExistingRequests(monitor);
}

View File

@ -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" +

View File

@ -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);
}

View File

@ -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);
});

View File

@ -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,<p>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() {