--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -66,16 +66,17 @@ class RequestListContent extends Compone
componentWillMount() {
const { dispatch, connector } = this.props;
this.contextMenu = new RequestListContextMenu({
cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
getTabTarget: connector.getTabTarget,
getLongString: connector.getLongString,
openStatistics: (open) => dispatch(Actions.openStatistics(connector, open)),
+ requestData: connector.requestData,
});
this.tooltip = new HTMLTooltip(window.parent.document, { type: "arrow" });
}
componentDidMount() {
// Install event handler for displaying a tooltip
this.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, {
toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
--- a/devtools/client/netmonitor/src/components/RequestListItem.js
+++ b/devtools/client/netmonitor/src/components/RequestListItem.js
@@ -43,17 +43,16 @@ const { div } = DOM;
* relevant for rendering the RequestListItem. Other properties (like request and
* response headers, cookies, bodies) are ignored. These are very useful for the
* network details, but not here.
*/
const UPDATED_REQ_ITEM_PROPS = [
"mimeType",
"eventTimings",
"securityState",
- "responseContentDataUri",
"status",
"statusText",
"fromCache",
"fromServiceWorker",
"method",
"url",
"remoteAddress",
"cause",
--- a/devtools/client/netmonitor/src/components/ResponsePanel.js
+++ b/devtools/client/netmonitor/src/components/ResponsePanel.js
@@ -36,16 +36,17 @@ const JSON_VIEW_MIME_TYPE = "application
* Response panel component
* Displays the GET parameters and POST data of a request
*/
class ResponsePanel extends Component {
static get propTypes() {
return {
request: PropTypes.object.isRequired,
openLink: PropTypes.func,
+ connector: PropTypes.object.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
imageDimensions: {
@@ -53,16 +54,46 @@ class ResponsePanel extends Component {
height: 0,
},
};
this.updateImageDimemsions = this.updateImageDimemsions.bind(this);
this.isJSON = this.isJSON.bind(this);
}
+ /**
+ * `componentDidMount` is called when opening the ResponsePanel for the first time
+ */
+ componentDidMount() {
+ this.maybeFetchResponseContent(this.props);
+ }
+
+ /**
+ * `componentWillReceiveProps` is the only method called when switching between two
+ * requests while the response panel is displayed.
+ */
+ componentWillReceiveProps(nextProps) {
+ this.maybeFetchResponseContent(nextProps);
+ }
+
+ /**
+ * When switching to another request, lazily fetch response content
+ * from the backend. The Response Panel will first be empty and then
+ * display the content.
+ */
+ maybeFetchResponseContent(props) {
+ if (props.request.responseContentAvailable &&
+ (!props.request.responseContent ||
+ !props.request.responseContent.content)) {
+ // This method will set `props.request.responseContent.content`
+ // asynchronously and force another render.
+ props.connector.requestData(props.request.id, "responseContent");
+ }
+ }
+
updateImageDimemsions({ target }) {
this.setState({
imageDimensions: {
width: target.naturalWidth,
height: target.naturalHeight,
},
});
}
--- a/devtools/client/netmonitor/src/components/TabboxPanel.js
+++ b/devtools/client/netmonitor/src/components/TabboxPanel.js
@@ -73,17 +73,17 @@ function TabboxPanel({
title: PARAMS_TITLE,
},
ParamsPanel({ connector, openLink, request }),
),
TabPanel({
id: PANELS.RESPONSE,
title: RESPONSE_TITLE,
},
- ResponsePanel({ request, openLink }),
+ ResponsePanel({ request, openLink, connector }),
),
TabPanel({
id: PANELS.TIMINGS,
title: TIMINGS_TITLE,
},
TimingsPanel({ request }),
),
request.cause && request.cause.stacktrace && request.cause.stacktrace.length > 0 &&
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -17,16 +17,17 @@ class FirefoxConnector {
this.willNavigate = this.willNavigate.bind(this);
this.displayCachedEvents = this.displayCachedEvents.bind(this);
this.onDocLoadingMarker = this.onDocLoadingMarker.bind(this);
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
this.setPreferences = this.setPreferences.bind(this);
this.triggerActivity = this.triggerActivity.bind(this);
this.getTabTarget = this.getTabTarget.bind(this);
this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
+ this.requestData = this.requestData.bind(this);
// Internals
this.getLongString = this.getLongString.bind(this);
this.getNetworkRequest = this.getNetworkRequest.bind(this);
}
async connect(connection, actions, getState) {
this.actions = actions;
@@ -280,11 +281,15 @@ class FirefoxConnector {
* @param {string} sourceURL source url
* @param {number} sourceLine source line number
*/
viewSourceInDebugger(sourceURL, sourceLine) {
if (this.toolbox) {
this.toolbox.viewSourceInDebugger(sourceURL, sourceLine);
}
}
+
+ requestData(request, type) {
+ return this.dataProvider.requestData(request, type);
+ }
}
module.exports = new FirefoxConnector();
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -25,16 +25,20 @@ class FirefoxDataProvider {
// Options
this.webConsoleClient = webConsoleClient;
this.actions = actions;
// Internal properties
this.payloadQueue = [];
this.rdpRequestMap = new Map();
+ // Map[key string => Promise] used by `requestData` to prevent requesting the same
+ // request data twice.
+ this.lazyRequestData = new Map();
+
// Fetching data from the backend
this.getLongString = this.getLongString.bind(this);
// Event handlers
this.onNetworkEvent = this.onNetworkEvent.bind(this);
this.onNetworkEventUpdate = this.onNetworkEventUpdate.bind(this);
}
@@ -112,16 +116,18 @@ class FirefoxDataProvider {
requestHeadersObj,
responseHeadersObj,
postDataObj,
requestCookiesObj,
responseCookiesObj
);
this.pushRequestToQueue(id, payload);
+
+ return payload;
}
async fetchResponseContent(mimeType, responseContent) {
let payload = {};
if (mimeType && responseContent && responseContent.content) {
let { encoding, text } = responseContent.content;
let response = await this.getLongString(text);
@@ -238,20 +244,34 @@ class FirefoxDataProvider {
* @return {boolean} return whether a specific networkEvent has been updated completely.
*/
isRequestPayloadReady(id) {
let record = this.rdpRequestMap.get(id);
if (!record) {
return false;
}
- // The payload is ready when all values in the record are true.
- // (i.e. all data received).
- let props = Object.getOwnPropertyNames(record);
- return props.every(prop => record[prop] === true);
+ let { payload } = this.getRequestFromQueue(id);
+
+ // The payload is ready when all values in the record are true. (i.e. all data
+ // received, but the lazy one. responseContent is the only one for now).
+ // Note that we never fetch response header/cookies for request with security issues.
+ // (Be careful, securityState can be undefined, for example for WebSocket requests)
+ // Also note that service worker don't have security info set.
+ // Bug 1404917 should simplify this heuristic by making all these field be lazily
+ // fetched, only on-demand.
+ return record.requestHeaders &&
+ record.requestCookies &&
+ record.eventTimings &&
+ (record.securityInfo || record.fromServiceWorker) &&
+ (
+ (record.responseHeaders && record.responseCookies) ||
+ payload.securityState == "broken" ||
+ (payload.responseContentAvailable && !payload.status)
+ );
}
/**
* Merge upcoming networkEventUpdate payload into existing one.
*
* @param {string} id request id
* @param {object} payload request data payload
*/
@@ -314,18 +334,24 @@ class FirefoxDataProvider {
},
startedDateTime,
} = networkInfo;
// Create tracking record for this request.
this.rdpRequestMap.set(actor, {
requestHeaders: false,
requestCookies: false,
+ responseHeaders: false,
+ responseCookies: false,
+ securityInfo: false,
eventTimings: false,
- responseContent: false,
+
+ // This isn't a request data, but we need to know about request being served from
+ // service worker later, from isRequestPayloadReady.
+ fromServiceWorker,
});
this.addRequest(actor, {
cause,
fromCache,
fromServiceWorker,
isXHR,
method,
@@ -343,177 +369,204 @@ class FirefoxDataProvider {
* @param {object} packet the message received from the server.
* @param {object} networkInfo the network request information.
*/
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":
- this.requestData(actor, updateType).then(response => {
- this.onRequestHeaders(response)
- .then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
- });
- break;
case "requestCookies":
- this.requestData(actor, updateType).then(response => {
- this.onRequestCookies(response)
- .then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
- });
- break;
case "requestPostData":
- this.requestData(actor, updateType).then(response => {
- this.onRequestPostData(response)
- .then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
- });
+ case "responseHeaders":
+ case "responseCookies":
+ this.requestPayloadData(actor, updateType);
break;
case "securityInfo":
this.updateRequest(actor, {
securityState: networkInfo.securityInfo,
}).then(() => {
- this.requestData(actor, updateType).then(response => {
- this.onSecurityInfo(response)
- .then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_SECURITY_INFO, actor);
- });
- });
- break;
- case "responseHeaders":
- this.requestData(actor, updateType).then(response => {
- this.onResponseHeaders(response)
- .then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
- });
- break;
- case "responseCookies":
- this.requestData(actor, updateType).then(response => {
- this.onResponseCookies(response)
- .then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
+ this.requestPayloadData(actor, updateType);
});
break;
case "responseStart":
this.updateRequest(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);
});
break;
case "responseContent":
- this.requestData(actor, updateType).then(response => {
- this.onResponseContent({
- contentSize: networkInfo.response.bodySize,
- transferredSize: networkInfo.response.transferredSize,
- mimeType: networkInfo.response.content.mimeType
- }, response).then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
+ this.updateRequest(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.updateRequest(actor, { totalTime: networkInfo.totalTime })
.then(() => {
- this.requestData(actor, updateType).then(response => {
- this.onEventTimings(response)
- .then(() => this.onDataReceived(actor, updateType));
- emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
- });
+ this.requestPayloadData(actor, updateType);
});
break;
}
emit(EVENTS.NETWORK_EVENT_UPDATED, actor);
}
/**
- * Wrapper method for requesting HTTP details data from the backend.
+ * 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.
*
- * It collects all RDP requests and monitors responses, so it's
- * possible to determine whether (and when) all requested data
- * has been fetched from the backend.
+ * All these requests are cached into `rdpRequestMap`. All requests related to a given
+ * actor will be collected in the same record.
*
- * It also nicely returns a promise.
+ * 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
- *
- * @return {Promise} return a promise resolved when data are received.
*/
- requestData(actor, method) {
+ requestPayloadData(actor, method) {
let record = this.rdpRequestMap.get(actor);
- // All RDP requests related to the given actor will be collected
- // in the same record.
- if (!record) {
- record = {};
- }
-
- // If data has been already requested return the same promise.
- if (record.method) {
- return record.method;
+ // If data has been already requested, do nothing.
+ if (record[method]) {
+ return;
}
- // Calculate real name of the client getter.
- let realMethodName = "get" + method.charAt(0).toUpperCase() +
- method.slice(1);
-
- // Request data from the backend.
- let promise = new Promise((resolve, reject) => {
- if (typeof this.webConsoleClient[realMethodName] == "function") {
- this.webConsoleClient[realMethodName](actor, response => {
- // Resolve incoming HTTP details data-promise.
- resolve(response);
- });
- } else {
- reject(new Error("Error: No such client method!"));
- }
+ 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);
});
-
- // Store the promise in order to know about RDP requests
- // in progress.
- record[method] = promise;
-
- return promise;
}
/**
* Executed when new data are received from the backend.
*/
- async onDataReceived(actor, type) {
- let record = this.rdpRequestMap.get(actor);
- if (record) {
- record[type] = true;
- }
-
+ 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);
let { updateRequest } = this.actions;
if (updateRequest) {
await 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);
}
}
/**
+ * Public connector API to lazily request HTTP details from the backend.
+ *
+ * This is internal method that focus on:
+ * - calling the right actor method,
+ * - emitting an event to tell we start fetching some request data,
+ * - call data processing method.
+ *
+ * @param {string} actor actor id (used as request id)
+ * @param {string} method identifier of the data we want to fetch
+ *
+ * @return {Promise} return a promise resolved when data is received.
+ */
+ requestData(actor, method) {
+ // Key string used in `lazyRequestData`. We use this Map to prevent requesting
+ // the same data twice at the same time.
+ let key = actor + "-" + method;
+ let promise = this.lazyRequestData.get(key);
+ // If a request is pending, reuse it.
+ if (promise) {
+ return promise;
+ }
+ // Fetch the data
+ promise = this._requestData(actor, method);
+ this.lazyRequestData.set(key, promise);
+ promise.then(async () => {
+ // Remove the request from the cache, any new call to requestData will fetch the
+ // data again.
+ this.lazyRequestData.delete(key, promise);
+
+ let payloadFromQueue = this.getRequestFromQueue(actor).payload;
+ let { updateRequest } = this.actions;
+ if (updateRequest) {
+ await updateRequest(actor, payloadFromQueue, true);
+ }
+ });
+ return promise;
+ }
+
+ /**
+ * Internal helper used to request HTTP details from the backend.
+ *
+ * This is internal method that focus on:
+ * - calling the right actor method,
+ * - emitting an event to tell we start fetching some request data,
+ * - call data processing method.
+ *
+ * @param {string} actor actor id (used as request id)
+ * @param {string} method identifier of the data we want to fetch
+ *
+ * @return {Promise} return a promise resolved when data is received.
+ */
+ async _requestData(actor, method) {
+ // Calculate real name of the client getter.
+ let clientMethodName = "get" + method.charAt(0).toUpperCase() +
+ method.slice(1);
+ // The name of the callback that processes request response
+ let callbackMethodName = "on" + method.charAt(0).toUpperCase() +
+ method.slice(1);
+ // And the event to fire before updating this data
+ let updatingEventName = "UPDATING_" + method.replace(/([A-Z])/g, "_$1").toUpperCase();
+
+ if (typeof this.webConsoleClient[clientMethodName] == "function") {
+ // Emit event that tell we just start fetching some data
+ emit(EVENTS[updatingEventName], actor);
+
+ // Do a RDP request to fetch data from the actor.
+ let response = await this.webConsoleClient[clientMethodName](actor);
+
+ // Call data processing method.
+ response = await this[callbackMethodName](response);
+ return response;
+ }
+ throw new Error("Error: No such client method '" + clientMethodName + "'!");
+ }
+
+ /**
* Handles additional information received for a "requestHeaders" packet.
*
* @param {object} response the message received from the server.
*/
onRequestHeaders(response) {
return this.updateRequest(response.from, {
requestHeaders: response
}).then(() => {
@@ -582,26 +635,30 @@ class FirefoxDataProvider {
return this.updateRequest(response.from, {
responseCookies: response
}).then(() => {
emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
});
}
/**
- * Handles additional information received for a "responseContent" packet.
+ * Handles additional information received via "getResponseContent" request.
*
- * @param {object} data the message received from the server event.
* @param {object} response the message received from the server.
*/
- onResponseContent(data, response) {
- let payload = Object.assign({ responseContent: response }, data);
- return this.updateRequest(response.from, payload).then(() => {
- emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
+ async onResponseContent(response) {
+ let payload = await this.updateRequest(response.from, {
+ // We have to ensure passing mimeType as fetchResponseContent needs it from
+ // updateRequest. It will convert the LongString in `response.content.text` to a
+ // string.
+ mimeType: response.content.mimeType,
+ responseContent: response,
});
+ emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
+ return payload.responseContent;
}
/**
* Handles additional information received for a "eventTimings" packet.
*
* @param {object} response the message received from the server.
*/
onEventTimings(response) {
--- a/devtools/client/netmonitor/src/connector/index.js
+++ b/devtools/client/netmonitor/src/connector/index.js
@@ -20,16 +20,17 @@ class Connector {
this.connectFirefox = this.connectFirefox.bind(this);
this.getLongString = this.getLongString.bind(this);
this.getNetworkRequest = this.getNetworkRequest.bind(this);
this.getTabTarget = this.getTabTarget.bind(this);
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
this.setPreferences = this.setPreferences.bind(this);
this.triggerActivity = this.triggerActivity.bind(this);
this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
+ this.requestData = this.requestData.bind(this);
}
// Connect/Disconnect API
connect(connection, actions, getState) {
if (!connection || !connection.tab) {
return;
}
@@ -93,11 +94,15 @@ class Connector {
triggerActivity() {
return this.connector.triggerActivity(...arguments);
}
viewSourceInDebugger() {
return this.connector.viewSourceInDebugger(...arguments);
}
+
+ requestData() {
+ return this.connector.requestData(...arguments);
+ }
}
module.exports.Connector = Connector;
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -119,17 +119,17 @@ const UPDATE_PROPS = [
"customQueryValue",
"requestHeaders",
"requestHeadersFromUploadStream",
"requestCookies",
"requestPostData",
"responseHeaders",
"responseCookies",
"responseContent",
- "responseContentDataUri",
+ "responseContentAvailable",
"formDataSections",
];
const PANELS = {
COOKIES: "cookies",
HEADERS: "headers",
PARAMS: "params",
RESPONSE: "response",
--- a/devtools/client/netmonitor/src/har/har-automation.js
+++ b/devtools/client/netmonitor/src/har/har-automation.js
@@ -163,16 +163,17 @@ HarAutomation.prototype = {
* Network panel (asynchronously) and saves it into a file.
*/
executeExport: function (data) {
let items = this.collector.getItems();
let form = this.toolbox.target.form;
let title = form.title || form.url;
let options = {
+ requestData: null,
getString: this.getString.bind(this),
view: this,
items: items,
};
options.defaultFileName = data.fileName;
options.compress = data.compress;
options.title = data.title || title;
--- a/devtools/client/netmonitor/src/har/har-builder.js
+++ b/devtools/client/netmonitor/src/har/har-builder.js
@@ -46,30 +46,32 @@ HarBuilder.prototype = {
/**
* This is the main method used to build the entire result HAR data.
* The process is asynchronous since it can involve additional RDP
* communication (e.g. resolving long strings).
*
* @returns {Promise} A promise that resolves to the HAR object when
* the entire build process is done.
*/
- build: function () {
+ build: async function () {
this.promises = [];
// Build basic structure for data.
let log = this.buildLog();
// Build entries.
for (let file of this._options.items) {
- log.entries.push(this.buildEntry(log, file));
+ log.entries.push(await this.buildEntry(log, file));
}
// Some data needs to be fetched from the backend during the
// build process, so wait till all is done.
- return Promise.all(this.promises).then(() => ({ log }));
+ await Promise.all(this.promises);
+
+ return { log };
},
// Helpers
buildLog: function () {
return {
version: HAR_VERSION,
creator: {
@@ -105,26 +107,26 @@ HarBuilder.prototype = {
}
this._pageMap[id] = page = this.buildPage(file);
log.pages.push(page);
return page;
},
- buildEntry: function (log, file) {
+ buildEntry: async function (log, file) {
let page = this.getPage(log, file);
let entry = {};
entry.pageref = page.id;
entry.startedDateTime = dateToJSON(new Date(file.startedMillis));
entry.time = file.endedMillis - file.startedMillis;
entry.request = this.buildRequest(file);
- entry.response = this.buildResponse(file);
+ entry.response = await this.buildResponse(file);
entry.cache = this.buildCache(file);
entry.timings = file.eventTimings ? file.eventTimings.timings : {};
if (file.remoteAddress) {
entry.serverIPAddress = file.remoteAddress;
}
if (file.remotePort) {
@@ -281,34 +283,34 @@ HarBuilder.prototype = {
});
});
}
});
return postData;
},
- buildResponse: function (file) {
+ buildResponse: async function (file) {
let response = {
status: 0
};
// Arbitrary value if it's aborted to make sure status has a number
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.content = this.buildContent(file);
+ response.content = await this.buildContent(file);
let headers = responseHeaders ? responseHeaders.headers : null;
let headersSize = responseHeaders ? responseHeaders.headersSize : -1;
response.redirectURL = findValue(headers, "Location");
response.headersSize = headersSize;
// 'bodySize' is size of the received response body in bytes.
@@ -318,23 +320,29 @@ HarBuilder.prototype = {
response.bodySize = (response.status == 304) ? 0 : -1;
} else {
response.bodySize = file.transferredSize;
}
return response;
},
- buildContent: function (file) {
+ buildContent: async function (file) {
let content = {
mimeType: file.mimeType,
size: -1
};
+ // When using HarAutomation, HarCollector will automatically fetch responseContent,
+ // but when we use it from netmonitor, FirefoxDataProvider should fetch it itself
+ // lazily, via requestData.
let responseContent = file.responseContent;
+ if (!responseContent && this._options.requestData) {
+ responseContent = await this._options.requestData(file.id, "responseContent");
+ }
if (responseContent && responseContent.content) {
content.size = responseContent.content.size;
content.encoding = responseContent.content.encoding;
}
let includeBodies = this._options.includeResponseBodies;
let contentDiscarded = responseContent ?
responseContent.contentDiscarded : false;
--- a/devtools/client/netmonitor/src/har/test/browser.ini
+++ b/devtools/client/netmonitor/src/har/test/browser.ini
@@ -1,14 +1,16 @@
[DEFAULT]
tags = devtools
subsuite = clipboard
skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
support-files =
head.js
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]
[browser_net_har_post_data.js]
[browser_net_har_throttle_upload.js]
[browser_net_har_post_data_on_get.js]
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
@@ -14,25 +14,26 @@ add_task(function* () {
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
let { connector, store, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let RequestListContextMenu = windowRequire(
"devtools/client/netmonitor/src/request-list-context-menu");
- let { getLongString, getTabTarget } = connector;
+ let { getLongString, getTabTarget, requestData } = connector;
store.dispatch(Actions.batchEnable(false));
let wait = waitForNetworkEvents(monitor, 1);
tab.linkedBrowser.reload();
yield wait;
- let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
+ let contextMenu = new RequestListContextMenu({
+ getTabTarget, getLongString, requestData });
yield contextMenu.copyAllAsHar();
let jsonString = SpecialPowers.getClipboardData("text/unicode");
let har = JSON.parse(jsonString);
// Check out HAR log
isnot(har.log, null, "The HAR log must exist");
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
@@ -11,29 +11,30 @@ add_task(function* () {
HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
info("Starting test... ");
let { connector, store, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let RequestListContextMenu = windowRequire(
"devtools/client/netmonitor/src/request-list-context-menu");
- let { getLongString, getTabTarget } = connector;
+ let { getLongString, getTabTarget, requestData } = connector;
store.dispatch(Actions.batchEnable(false));
// Execute one POST request on the page and wait till its done.
let wait = waitForNetworkEvents(monitor, 0, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.executeTest();
});
yield wait;
// Copy HAR into the clipboard (asynchronous).
- let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
+ let contextMenu = new RequestListContextMenu({
+ getTabTarget, getLongString, requestData });
let jsonString = yield contextMenu.copyAllAsHar();
let har = JSON.parse(jsonString);
// Check out the HAR log.
isnot(har.log, null, "The HAR log must exist");
is(har.log.pages.length, 1, "There must be one page");
is(har.log.entries.length, 1, "There must be one request");
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_post_data_on_get.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data_on_get.js
@@ -11,29 +11,30 @@ add_task(function* () {
HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
info("Starting test... ");
let { connector, store, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let RequestListContextMenu = windowRequire(
"devtools/client/netmonitor/src/request-list-context-menu");
- let { getLongString, getTabTarget } = connector;
+ let { getLongString, getTabTarget, requestData } = connector;
store.dispatch(Actions.batchEnable(false));
// Execute one GET request on the page and wait till its done.
let wait = waitForNetworkEvents(monitor, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.executeTest3();
});
yield wait;
// Copy HAR into the clipboard (asynchronous).
- let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
+ let contextMenu = new RequestListContextMenu({
+ getTabTarget, getLongString, requestData });
let jsonString = yield contextMenu.copyAllAsHar();
let har = JSON.parse(jsonString);
// Check out the HAR log.
isnot(har.log, null, "The HAR log must exist");
is(har.log.pages.length, 1, "There must be one page");
is(har.log.entries.length, 1, "There must be one request");
--- 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
@@ -15,17 +15,17 @@ function* throttleUploadTest(actuallyThr
HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
let { connector, store, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let RequestListContextMenu = windowRequire(
"devtools/client/netmonitor/src/request-list-context-menu");
- let { getLongString, getTabTarget, setPreferences } = connector;
+ let { getLongString, getTabTarget, setPreferences, requestData } = connector;
store.dispatch(Actions.batchEnable(false));
const size = 4096;
const uploadSize = actuallyThrottle ? size / 3 : 0;
const request = {
"NetworkMonitor.throttleData": {
@@ -48,17 +48,18 @@ function* throttleUploadTest(actuallyThr
// Execute one POST request on the page and wait till its done.
let wait = waitForNetworkEvents(monitor, 0, 1);
yield ContentTask.spawn(tab.linkedBrowser, { size }, function* (args) {
content.wrappedJSObject.executeTest2(args.size);
});
yield wait;
// Copy HAR into the clipboard (asynchronous).
- let contextMenu = new RequestListContextMenu({ getTabTarget, getLongString });
+ let contextMenu = new RequestListContextMenu({
+ getTabTarget, getLongString, requestData });
let jsonString = yield contextMenu.copyAllAsHar();
let har = JSON.parse(jsonString);
// Check out the HAR log.
isnot(har.log, null, "The HAR log must exist");
is(har.log.pages.length, 1, "There must be one page");
is(har.log.entries.length, 1, "There must be one request");
--- a/devtools/client/netmonitor/src/reducers/requests.js
+++ b/devtools/client/netmonitor/src/reducers/requests.js
@@ -53,16 +53,17 @@ const Request = I.Record({
customQueryValue: undefined,
requestHeaders: undefined,
requestHeadersFromUploadStream: undefined,
requestCookies: undefined,
requestPostData: undefined,
responseHeaders: undefined,
responseCookies: undefined,
responseContent: undefined,
+ responseContentAvailable: false,
responseContentDataUri: undefined,
formDataSections: undefined,
});
const Requests = I.Record({
// The collection of requests (keyed by id)
requests: I.Map(),
// Selection state
--- a/devtools/client/netmonitor/src/request-list-context-menu.js
+++ b/devtools/client/netmonitor/src/request-list-context-menu.js
@@ -15,28 +15,31 @@ const {
getSortedRequests,
} = require("./selectors/index");
const { L10N } = require("./utils/l10n");
const { showMenu } = require("devtools/client/netmonitor/src/utils/menu");
const {
getUrlQuery,
parseQueryString,
getUrlBaseName,
+ formDataURI,
} = require("./utils/request-utils");
function RequestListContextMenu({
cloneSelectedRequest,
getLongString,
getTabTarget,
openStatistics,
+ requestData,
}) {
this.cloneSelectedRequest = cloneSelectedRequest;
this.getLongString = getLongString;
this.getTabTarget = getTabTarget;
this.openStatistics = openStatistics;
+ this.requestData = requestData;
}
RequestListContextMenu.prototype = {
get selectedRequest() {
// FIXME: Bug 1336382 - Implement RequestListContextMenu React component
// Remove window.store
return getSelectedRequest(window.store.getState());
},
@@ -109,20 +112,17 @@ RequestListContextMenu.prototype = {
visible: !!(selectedRequest && selectedRequest.responseHeaders),
click: () => this.copyResponseHeaders(),
});
copySubmenu.push({
id: "request-list-context-copy-response",
label: L10N.getStr("netmonitor.context.copyResponse"),
accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
- visible: !!(selectedRequest &&
- selectedRequest.responseContent &&
- selectedRequest.responseContent.content.text &&
- selectedRequest.responseContent.content.text.length !== 0),
+ visible: !!(selectedRequest && selectedRequest.responseContentAvailable),
click: () => this.copyResponse(),
});
copySubmenu.push({
id: "request-list-context-copy-image-as-data-uri",
label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
visible: !!(selectedRequest &&
@@ -333,25 +333,32 @@ RequestListContextMenu.prototype = {
rawHeaders = rawHeaders.replace(/\r/g, "");
}
copyString(rawHeaders);
},
/**
* Copy image as data uri.
*/
- copyImageAsDataUri() {
- copyString(this.selectedRequest.responseContentDataUri);
+ async copyImageAsDataUri() {
+ let responseContent = await this.requestData(this.selectedRequest.id,
+ "responseContent");
+ let { mimeType } = this.selectedRequest;
+ let { encoding, text } = responseContent.content;
+ let src = formDataURI(mimeType, encoding, text);
+ copyString(src);
},
/**
* Save image as.
*/
- saveImageAs() {
- let { encoding, text } = this.selectedRequest.responseContent.content;
+ async saveImageAs() {
+ let responseContent = await this.requestData(this.selectedRequest.id,
+ "responseContent");
+ let { encoding, text } = responseContent.content;
let fileName = getUrlBaseName(this.selectedRequest.url);
let data;
if (encoding === "base64") {
let decoded = atob(text);
data = new Uint8Array(decoded.length);
for (let i = 0; i < decoded.length; ++i) {
data[i] = decoded.charCodeAt(i);
}
@@ -359,18 +366,20 @@ RequestListContextMenu.prototype = {
data = text;
}
saveAs(new Blob([data]), fileName, document);
},
/**
* Copy response data as a string.
*/
- copyResponse() {
- copyString(this.selectedRequest.responseContent.content.text);
+ async copyResponse() {
+ let responseContent = await this.requestData(this.selectedRequest.id,
+ "responseContent");
+ copyString(responseContent.content.text);
},
/**
* Copy HAR from the network panel content to the clipboard.
*/
copyAllAsHar() {
return HarExporter.copy(this.getDefaultHarOptions());
},
@@ -386,16 +395,17 @@ RequestListContextMenu.prototype = {
return HarExporter.save(this.getDefaultHarOptions());
},
getDefaultHarOptions() {
let form = this.getTabTarget().form;
let title = form.title || form.url;
return {
+ requestData: this.requestData,
getString: this.getLongString,
items: this.sortedRequests,
title: title
};
}
};
module.exports = RequestListContextMenu;
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -1,14 +1,15 @@
[DEFAULT]
tags = devtools
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
html_image-tooltip-test-page.html
html_cors-test-page.html
html_custom-get-page.html
html_cyrillic-test-page.html
html_frame-test-page.html
--- a/devtools/client/netmonitor/test/browser_net_autoscroll.js
+++ b/devtools/client/netmonitor/test/browser_net_autoscroll.js
@@ -5,17 +5,17 @@
/**
* Bug 863102 - Automatically scroll down upon new network requests.
* edited to account for changes made to fix Bug 1360457
*/
add_task(function* () {
requestLongerTimeout(4);
- let { monitor } = yield initNetMonitor(INFINITE_GET_URL, true);
+ let { tab, monitor } = yield initNetMonitor(INFINITE_GET_URL, true);
let { document, windowRequire, store } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
store.dispatch(Actions.batchEnable(false));
// Wait until the first request makes the empty notice disappear
yield waitForRequestListToAppear();
@@ -52,16 +52,21 @@ add_task(function* () {
// from just below the headers.
store.dispatch(Actions.selectRequestByIndex(0));
yield waitForNetworkEvents(monitor, 8);
yield waitSomeTime();
let requestsContainerHeaders = requestsContainer.firstChild;
let headersHeight = requestsContainerHeaders.offsetHeight;
is(requestsContainer.scrollTop, headersHeight, "Did not scroll.");
+ // Stop doing requests.
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function () {
+ content.wrappedJSObject.stopRequests();
+ });
+
// Done: clean up.
return teardown(monitor);
function waitForRequestListToAppear() {
info("Waiting until the empty notice disappears and is replaced with the list");
return waitUntil(() => !!document.querySelector(".requests-list-contents"));
}
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -46,21 +46,23 @@ add_task(function* () {
type: "plain",
fullMimeType: "text/plain",
transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 60),
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
time: true
});
wait = waitForDOM(document, ".CodeMirror-code");
+ let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector(".network-details-panel-toggle"));
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector("#response-tab"));
yield wait;
+ yield onResponseContent;
yield testResponse("br");
yield teardown(monitor);
function* testResponse(type) {
switch (type) {
case "br": {
is(document.querySelector(".CodeMirror-line").textContent, "X".repeat(64),
"The text shown in the source editor is incorrect for the brotli request.");
--- a/devtools/client/netmonitor/test/browser_net_clear.js
+++ b/devtools/client/netmonitor/test/browser_net_clear.js
@@ -8,40 +8,39 @@
*/
add_task(function* () {
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
let { document, store, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
- let { EVENTS } = windowRequire("devtools/client/netmonitor/src/constants");
let detailsPanelToggleButton = document.querySelector(".network-details-panel-toggle");
let clearButton = document.querySelector(".requests-list-clear-button");
store.dispatch(Actions.batchEnable(false));
// Make sure we start in a sane state
assertNoRequestState();
// Load one request and assert it shows up in the list
- let networkEvent = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
+ let onMonitorUpdated = waitForAllRequestsFinished(monitor);
tab.linkedBrowser.reload();
- yield networkEvent;
+ yield onMonitorUpdated;
assertSingleRequestState();
// Click clear and make sure the requests are gone
EventUtils.sendMouseEvent({ type: "click" }, clearButton);
assertNoRequestState();
// Load a second request and make sure they still show up
- networkEvent = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
+ onMonitorUpdated = waitForAllRequestsFinished(monitor);
tab.linkedBrowser.reload();
- yield networkEvent;
+ yield onMonitorUpdated;
assertSingleRequestState();
// Make sure we can now open the network details panel
EventUtils.sendMouseEvent({ type: "click" }, detailsPanelToggleButton);
ok(document.querySelector(".network-details-panel") &&
!detailsPanelToggleButton.classList.contains("pane-collapsed"),
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -17,17 +17,17 @@ add_task(function* () {
let {
getDisplayedRequests,
getSortedRequests,
} = windowRequire("devtools/client/netmonitor/src/selectors/index");
store.dispatch(Actions.batchEnable(false));
let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
- yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function () {
content.wrappedJSObject.performRequests();
});
yield wait;
for (let requestItem of document.querySelectorAll(".request-list-item")) {
let requestsListStatus = requestItem.querySelector(".requests-list-status");
requestItem.scrollIntoView();
EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
@@ -137,35 +137,35 @@ add_task(function* () {
type: "plain",
fullMimeType: "text/plain",
transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 324),
size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
time: true
}
);
- yield selectIndexAndWaitForSourceEditor(0);
+ yield selectIndexAndWaitForSourceEditor(monitor, 0);
yield testResponseTab("xml");
- yield selectIndexAndWaitForSourceEditor(1);
+ yield selectIndexAndWaitForSourceEditor(monitor, 1);
yield testResponseTab("css");
- yield selectIndexAndWaitForSourceEditor(2);
+ yield selectIndexAndWaitForSourceEditor(monitor, 2);
yield testResponseTab("js");
yield selectIndexAndWaitForJSONView(3);
yield testResponseTab("json");
- yield selectIndexAndWaitForSourceEditor(4);
+ yield selectIndexAndWaitForSourceEditor(monitor, 4);
yield testResponseTab("html");
yield selectIndexAndWaitForImageView(5);
yield testResponseTab("png");
- yield selectIndexAndWaitForSourceEditor(6);
+ yield selectIndexAndWaitForSourceEditor(monitor, 6);
yield testResponseTab("gzip");
yield teardown(monitor);
function* testResponseTab(type) {
let tabpanel = document.querySelector("#response-panel");
function checkVisibility(box) {
@@ -265,37 +265,31 @@ add_task(function* () {
is(text, new Array(1000).join("Hello gzip!"),
"The text shown in the source editor is incorrect for the gzip request.");
break;
}
}
}
- function* selectIndexAndWaitForSourceEditor(index) {
- let editor = document.querySelector("#response-panel .CodeMirror-code");
- if (!editor) {
- let waitDOM = waitForDOM(document, "#response-panel .CodeMirror-code");
- EventUtils.sendMouseEvent({ type: "mousedown" },
- document.querySelectorAll(".request-list-item")[index]);
- document.querySelector("#response-tab").click();
- yield waitDOM;
- } else {
- EventUtils.sendMouseEvent({ type: "mousedown" },
- document.querySelectorAll(".request-list-item")[index]);
- }
- }
-
function* selectIndexAndWaitForJSONView(index) {
+ let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
let tabpanel = document.querySelector("#response-panel");
let waitDOM = waitForDOM(tabpanel, ".treeTable");
store.dispatch(Actions.selectRequestByIndex(index));
yield waitDOM;
+ yield onResponseContent;
+
+ // Waiting for RECEIVED_RESPONSE_CONTENT isn't enough.
+ // DOM may not be fully updated yet and checkVisibility(json) may still fail.
+ yield waitForTick();
}
function* selectIndexAndWaitForImageView(index) {
+ let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
let tabpanel = document.querySelector("#response-panel");
let waitDOM = waitForDOM(tabpanel, ".response-image");
store.dispatch(Actions.selectRequestByIndex(index));
let [imageNode] = yield waitDOM;
yield once(imageNode, "load");
+ yield onResponseContent;
}
});
--- a/devtools/client/netmonitor/test/browser_net_headers-alignment.js
+++ b/devtools/client/netmonitor/test/browser_net_headers-alignment.js
@@ -5,17 +5,17 @@
/**
* Bug 1360457 - Mis-alignment between headers and columns on overflow
*/
add_task(function* () {
requestLongerTimeout(4);
- let { monitor } = yield initNetMonitor(INFINITE_GET_URL, true);
+ let { tab, monitor } = yield initNetMonitor(INFINITE_GET_URL, true);
let { document, windowRequire, store } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
store.dispatch(Actions.batchEnable(false));
// Wait until the first request makes the empty notice disappear
yield waitForRequestListToAppear();
@@ -35,16 +35,21 @@ add_task(function* () {
let aHeaderColumn = headers.childNodes[columnNumber];
let aRequestColumn = firstRequestLine.childNodes[columnNumber];
is(aHeaderColumn.getBoundingClientRect().left,
aRequestColumn.getBoundingClientRect().left,
"Headers for columns number " + columnNumber + " are aligned."
);
}
+ // Stop doing requests.
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.stopRequests();
+ });
+
// Done: clean up.
return teardown(monitor);
function waitForRequestListToAppear() {
info("Waiting until the empty notice disappears and is replaced with the list");
return waitUntil(() => !!document.querySelector(".requests-list-contents"));
}
--- a/devtools/client/netmonitor/test/browser_net_resend_cors.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -51,25 +51,29 @@ add_task(function* () {
info("Sending the cloned request (without change)");
store.dispatch(Actions.sendCustomRequest(connector));
});
info("Waiting for both resent requests");
yield onRequests;
// Check the resent requests
- ITEMS.forEach((item, i) => {
+ for (let i = 0; i < ITEMS.length; i++) {
+ let item = ITEMS[i];
is(item.method, METHODS[i], `The ${item.method} request has the right method`);
is(item.url, requestUrl, `The ${item.method} request has the right URL`);
is(item.status, 200, `The ${item.method} response has the right status`);
if (item.method === "POST") {
+ // Force fetching response content
+ let responseContent = yield connector.requestData(item.id, "responseContent");
+
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(item.responseContent.content.text, "Access-Control-Allow-Origin: *",
+ is(responseContent.content.text, "Access-Control-Allow-Origin: *",
"The POST response has the right content");
}
- });
+ }
info("Finishing the test");
return teardown(monitor);
});
--- a/devtools/client/netmonitor/test/browser_net_security-error.js
+++ b/devtools/client/netmonitor/test/browser_net_security-error.js
@@ -42,19 +42,16 @@ add_task(function* () {
* completed.
*/
function waitForSecurityBrokenNetworkEvent() {
let awaitedEvents = [
"UPDATING_REQUEST_HEADERS",
"RECEIVED_REQUEST_HEADERS",
"UPDATING_REQUEST_COOKIES",
"RECEIVED_REQUEST_COOKIES",
- "STARTED_RECEIVING_RESPONSE",
- "UPDATING_RESPONSE_CONTENT",
- "RECEIVED_RESPONSE_CONTENT",
"UPDATING_EVENT_TIMINGS",
"RECEIVED_EVENT_TIMINGS",
"UPDATING_SECURITY_INFO",
"RECEIVED_SECURITY_INFO",
];
let promises = awaitedEvents.map((event) => {
return monitor.panelWin.once(EVENTS[event]);
--- a/devtools/client/netmonitor/test/browser_net_security-state.js
+++ b/devtools/client/netmonitor/test/browser_net_security-state.js
@@ -75,49 +75,40 @@ add_task(function* () {
yield executeRequests(1, "http://test1.example.com" + CORS_SJS_PATH);
yield done;
done = waitForNetworkEvents(monitor, 1);
info("Requesting a resource over HTTPS.");
yield executeRequests(1, "https://example.com" + CORS_SJS_PATH);
yield done;
- done = waitForSecurityBrokenNetworkEvent(true);
+ done = waitForSecurityBrokenNetworkEvent();
info("Requesting a resource over HTTP to localhost.");
yield executeRequests(1, "http://localhost" + CORS_SJS_PATH);
yield done;
const expectedCount = Object.keys(EXPECTED_SECURITY_STATES).length;
is(store.getState().requests.requests.size,
expectedCount,
expectedCount + " events logged.");
}
/**
* Returns a promise that's resolved once a request with security issues is
* completed.
*/
- function waitForSecurityBrokenNetworkEvent(networkError) {
+ function waitForSecurityBrokenNetworkEvent() {
let awaitedEvents = [
"UPDATING_REQUEST_HEADERS",
"RECEIVED_REQUEST_HEADERS",
"UPDATING_REQUEST_COOKIES",
"RECEIVED_REQUEST_COOKIES",
- "STARTED_RECEIVING_RESPONSE",
- "UPDATING_RESPONSE_CONTENT",
- "RECEIVED_RESPONSE_CONTENT",
"UPDATING_EVENT_TIMINGS",
"RECEIVED_EVENT_TIMINGS",
];
- // If the reason for breakage is a network error, then the
- // STARTED_RECEIVING_RESPONSE event does not fire.
- if (networkError) {
- awaitedEvents = awaitedEvents.filter(e => e !== "STARTED_RECEIVING_RESPONSE");
- }
-
let promises = awaitedEvents.map((event) => {
return monitor.panelWin.once(EVENTS[event]);
});
return Promise.all(promises);
}
});
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -93,19 +93,16 @@ add_task(function* () {
* completed.
*/
function waitForSecurityBrokenNetworkEvent() {
let awaitedEvents = [
"UPDATING_REQUEST_HEADERS",
"RECEIVED_REQUEST_HEADERS",
"UPDATING_REQUEST_COOKIES",
"RECEIVED_REQUEST_COOKIES",
- "STARTED_RECEIVING_RESPONSE",
- "UPDATING_RESPONSE_CONTENT",
- "RECEIVED_RESPONSE_CONTENT",
"UPDATING_EVENT_TIMINGS",
"RECEIVED_EVENT_TIMINGS",
];
let promises = awaitedEvents.map((event) => {
return monitor.panelWin.once(EVENTS[event]);
});
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -245,50 +245,34 @@ function test() {
SIMPLE_SJS,
{
status: "200",
statusText: "Och Aye"
}
);
});
- expectEvent(EVENTS.RECEIVED_RESPONSE_CONTENT, async () => {
+ expectEvent(EVENTS.PAYLOAD_READY, async () => {
await waitUntil(() => {
let requestItem = getSortedRequests(store.getState()).get(0);
return requestItem &&
requestItem.transferredSize &&
requestItem.contentSize &&
- requestItem.mimeType &&
- requestItem.responseContent;
+ requestItem.mimeType;
});
let requestItem = getSortedRequests(store.getState()).get(0);
is(requestItem.transferredSize, "342",
"The transferredSize data has an incorrect value.");
is(requestItem.contentSize, "12",
"The contentSize data has an incorrect value.");
is(requestItem.mimeType, "text/plain; charset=utf-8",
"The mimeType data has an incorrect value.");
- ok(requestItem.responseContent,
- "There should be a responseContent data available.");
- // eslint-disable-next-line mozilla/no-cpows-in-tests
- is(requestItem.responseContent.content.mimeType,
- "text/plain; charset=utf-8",
- "The responseContent data has an incorrect |content.mimeType| property.");
- // eslint-disable-next-line mozilla/no-cpows-in-tests
- is(requestItem.responseContent.content.text,
- "Hello world!",
- "The responseContent data has an incorrect |content.text| property.");
- // eslint-disable-next-line mozilla/no-cpows-in-tests
- is(requestItem.responseContent.content.size,
- 12,
- "The responseContent data has an incorrect |content.size| property.");
-
verifyRequestItemTarget(
document,
getDisplayedRequests(store.getState()),
requestItem,
"GET",
SIMPLE_SJS,
{
type: "plain",
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -60,37 +60,23 @@ add_task(function* () {
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector(".network-details-panel-toggle"));
EventUtils.sendMouseEvent({ type: "click" },
document.querySelector("#response-tab"));
yield wait;
store.dispatch(Actions.selectRequest(null));
- yield selectIndexAndWaitForSourceEditor(0);
+ yield selectIndexAndWaitForSourceEditor(monitor, 0);
// the hls-m3u8 part
testEditorContent(REQUESTS[0]);
- yield selectIndexAndWaitForSourceEditor(1);
+ yield selectIndexAndWaitForSourceEditor(monitor, 1);
// the mpeg-dash part
testEditorContent(REQUESTS[1]);
return teardown(monitor);
- function* selectIndexAndWaitForSourceEditor(index) {
- let editor = document.querySelector("#response-panel .CodeMirror-code");
- if (!editor) {
- let waitDOM = waitForDOM(document, "#response-panel .CodeMirror-code");
- EventUtils.sendMouseEvent({ type: "mousedown" },
- document.querySelectorAll(".request-list-item")[index]);
- document.querySelector("#response-tab").click();
- yield waitDOM;
- } else {
- EventUtils.sendMouseEvent({ type: "mousedown" },
- document.querySelectorAll(".request-list-item")[index]);
- }
- }
-
function testEditorContent([ fmt, textRe ]) {
ok(document.querySelector(".CodeMirror-line").textContent.match(textRe),
"The text shown in the source editor for " + fmt + " is correct.");
}
});
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -1,24 +1,28 @@
/* 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 shared-head.js */
/* exported Toolbox, restartNetMonitor, teardown, waitForExplicitFinish,
verifyRequestItemTarget, waitFor, testFilterButtons, loadCommonFrameScript,
- performRequestsInContent, waitForNetworkEvents */
+ performRequestsInContent, waitForNetworkEvents, selectIndexAndWaitForSourceEditor */
"use strict";
// shared-head.js handles imports, constants, and utility functions
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
this);
-const { EVENTS } = require("devtools/client/netmonitor/src/constants");
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/netmonitor/test/shared-head.js",
+ this);
+
const {
getFormattedIPAndPort,
getFormattedTime,
} = require("devtools/client/netmonitor/src/utils/format-utils");
const {
decodeUnicodeUrl,
getFormattedProtocol,
getUrlBaseName,
@@ -276,16 +280,22 @@ function restartNetMonitor(monitor, newU
}
function teardown(monitor) {
info("Destroying the specified network 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);
+ info("All pending requests finished.");
+
let onDestroyed = monitor.once("destroyed");
yield removeTab(tab);
yield onDestroyed;
});
}
function waitForNetworkEvents(monitor, getRequests, postRequests = 0) {
return new Promise((resolve) => {
@@ -301,23 +311,24 @@ function waitForNetworkEvents(monitor, g
["UPDATING_REQUEST_COOKIES", onGenericEvent],
["RECEIVED_REQUEST_COOKIES", onGenericEvent],
["UPDATING_REQUEST_POST_DATA", onPostEvent],
["RECEIVED_REQUEST_POST_DATA", onPostEvent],
["UPDATING_RESPONSE_HEADERS", onGenericEvent],
["RECEIVED_RESPONSE_HEADERS", onGenericEvent],
["UPDATING_RESPONSE_COOKIES", onGenericEvent],
["RECEIVED_RESPONSE_COOKIES", onGenericEvent],
- ["STARTED_RECEIVING_RESPONSE", onGenericEvent],
- ["UPDATING_RESPONSE_CONTENT", onGenericEvent],
- ["RECEIVED_RESPONSE_CONTENT", onGenericEvent],
["UPDATING_EVENT_TIMINGS", onGenericEvent],
["RECEIVED_EVENT_TIMINGS", onGenericEvent],
["PAYLOAD_READY", onPayloadReady]
];
+ let expectedGenericEvents = awaitedEventsToListeners
+ .filter(([, listener]) => listener == onGenericEvent).length;
+ let expectedPostEvents = awaitedEventsToListeners
+ .filter(([, listener]) => listener == onPostEvent).length;
function initProgressForURL(url) {
if (progress[url]) {
return;
}
progress[url] = {};
awaitedEventsToListeners.forEach(function ([e]) {
progress[url][e] = 0;
@@ -360,32 +371,34 @@ function waitForNetworkEvents(monitor, g
}
payloadReady++;
maybeResolve(event, actor, networkInfo);
}
function maybeResolve(event, actor, networkInfo) {
info("> Network events progress: " +
- genericEvents + "/" + ((getRequests + postRequests) * 13) + ", " +
- postEvents + "/" + (postRequests * 2) + ", " +
+ "Payload: " + payloadReady + "/" + (getRequests + postRequests) + ", " +
+ "Generic: " + genericEvents + "/" +
+ ((getRequests + postRequests) * expectedGenericEvents) + ", " +
+ "Post: " + postEvents + "/" + (postRequests * expectedPostEvents) + ", " +
"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 15 updates which need to be fired for a request to be
- // considered finished. The "requestPostData" packet isn't fired for
- // non-POST requests.
+ // 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 + postRequests) &&
- genericEvents >= (getRequests + postRequests) * 13 &&
- postEvents >= postRequests * 2) {
+ genericEvents >= (getRequests + postRequests) * expectedGenericEvents &&
+ postEvents >= postRequests * expectedPostEvents) {
awaitedEventsToListeners.forEach(([e, l]) => panel.off(EVENTS[e], l));
executeSoon(resolve);
}
}
awaitedEventsToListeners.forEach(([e, l]) => panel.on(EVENTS[e], l));
});
}
@@ -679,8 +692,30 @@ function waitForContentMessage(name) {
return new Promise((resolve) => {
mm.addMessageListener(name, function onMessage(msg) {
mm.removeMessageListener(name, onMessage);
resolve(msg);
});
});
}
+
+/**
+ * Select a request and switch to its response panel.
+ *
+ * @param {Number} index The request index to be selected
+ */
+async function selectIndexAndWaitForSourceEditor(monitor, index) {
+ let document = monitor.panelWin.document;
+ let onResponseContent = monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT);
+ // Select the request first, as it may try to fetch whatever is the current request's
+ // responseContent if we select the ResponseTab first.
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll(".request-list-item")[index]);
+ // We may already be on the ResponseTab, so only select it if needed.
+ let editor = document.querySelector("#response-panel .CodeMirror-code");
+ if (!editor) {
+ let waitDOM = waitForDOM(document, "#response-panel .CodeMirror-code");
+ document.querySelector("#response-tab").click();
+ await waitDOM;
+ }
+ await onResponseContent;
+}
--- a/devtools/client/netmonitor/test/html_infinite-get-page.html
+++ b/devtools/client/netmonitor/test/html_infinite-get-page.html
@@ -26,18 +26,24 @@
callback();
}
};
xhr.send(null);
}
// Use a count parameter to defeat caching.
let count = 0;
+ let doRequests = true;
+ function stopRequests() { // eslint-disable-line no-unused-vars
+ doRequests = false;
+ }
(function performRequests() {
get("request_" + (count++), function () {
- setTimeout(performRequests, 50);
+ if (doRequests) {
+ setTimeout(performRequests, 50);
+ }
});
})();
</script>
</body>
</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/shared-head.js
@@ -0,0 +1,43 @@
+/* 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.requestCookies ||
+ !request.eventTimings ||
+ (!request.securityInfo && !request.fromServiceWorker) ||
+ ((!request.responseHeaders || !request.responseCookies) &&
+ 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);
+ }
+}
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -53,16 +53,17 @@ support-files =
doc_xulpage.xul
sync.html
utf-16.css
!/devtools/client/commandline/test/helpers.js
!/devtools/client/framework/test/shared-head.js
!/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
[browser_styleeditor_add_stylesheet.js]
[browser_styleeditor_autocomplete.js]
[browser_styleeditor_autocomplete-disabled.js]
[browser_styleeditor_bom.js]
--- a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
@@ -1,16 +1,21 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
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* () {
// Disable rcwn to make cache behavior deterministic.
yield pushPref("network.http.rcwn.enabled", false);
info("Opening netmonitor");
let tab = yield addTab("about:blank");
@@ -29,16 +34,18 @@ add_task(function* () {
yield navigateTo(TEST_URL);
info("Opening Style Editor");
let styleeditor = yield toolbox.selectTool("styleeditor");
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())) {
if (item.url.endsWith("doc_uncached.css")) {
items.push(item);
}
}
--- a/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
@@ -111,16 +111,19 @@ function NetworkEventMessage({
getLongString: (grip) => {
return serviceContainer.getLongString(grip);
},
getTabTarget: () => {},
getNetworkRequest: () => {},
sendHTTPRequest: () => {},
setPreferences: () => {},
triggerActivity: () => {},
+ requestData: (requestId, dataType) => {
+ return serviceContainer.requestData(requestId, dataType);
+ },
};
// Only render the attachment if the network-event is
// actually opened (performance optimization).
const attachment = open && dom.div({
className: "network-info network-monitor devtools-monospace"},
TabboxPanel({
connector,
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -91,16 +91,19 @@ NewConsoleOutputWrapper.prototype = {
hud.owner.openLink(url);
},
createElement: nodename => {
return this.document.createElement(nodename);
},
getLongString: (grip) => {
return hud.proxy.webConsoleClient.getString(grip);
},
+ requestData(id, type) {
+ return hud.proxy.networkDataProvider.requestData(id, type);
+ },
};
// Set `openContextMenu` this way so, `serviceContainer` variable
// is available in the current scope and we can pass it into
// `createContextMenu` method.
serviceContainer.openContextMenu = (e, message) => {
let { screenX, screenY, target } = e;
--- a/devtools/client/webconsole/new-console-output/store.js
+++ b/devtools/client/webconsole/new-console-output/store.js
@@ -172,16 +172,23 @@ function enableNetProvider(hud) {
// Data provider implements async logic for fetching
// data from the backend. It's created the first
// time it's needed.
if (!dataProvider) {
dataProvider = new DataProvider({
actions,
webConsoleClient: proxy.webConsoleClient
});
+
+ // /!\ This is terrible, but it allows ResponsePanel to be able to call
+ // `dataProvider.requestData` to fetch response content lazily.
+ // `proxy.networkDataProvider` is put by NewConsoleOutputWrapper on
+ // `serviceContainer` which allow NetworkEventMessage to expose requestData on
+ // the fake `connector` object it hands over to ResponsePanel.
+ proxy.networkDataProvider = dataProvider;
}
let type = action.type;
let newState = reducer(state, action);
// If network message has been opened, fetch all HTTP details
// from the backend. It can happen (especially in test) that
// the message is opened before all network event updates are
--- 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
@@ -59,9 +59,11 @@ async function testNetmonitor(toolbox) {
await waitUntil(() => store.getState().requests.requests.size > 0);
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.");
+
+ await waitForExistingRequests(monitor);
}
--- 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
@@ -44,16 +44,18 @@ add_task(async function task() {
// Expand network log
urlNode.click();
await consoleReady;
info("network-request-payload-ready received");
await testNetworkMessage(messageNode);
+
+ await waitForExistingRequests(monitor);
});
async function testNetworkMessage(messageNode) {
let headersTab = messageNode.querySelector("#headers-tab");
ok(headersTab, "Headers tab is available");
// Headers tab should be selected by default, so just check its content.
--- 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
@@ -70,9 +70,12 @@ async function testNetmonitorLink(toolbo
store.dispatch(actions.batchEnable(false));
await waitUntil(() => {
const selected = getSelectedRequest(store.getState());
return selected && selected.url === url;
});
ok(true, "The attached url is correct.");
+
+ let monitor = toolbox.getCurrentPanel();
+ await waitForExistingRequests(monitor);
}
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -1,23 +1,27 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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";
// shared-head.js handles imports, constants, and utility functions
// Load the shared-head file first.
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" +
"&utm_medium=firefox-console-errors" +
"&utm_campaign=default";
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
registerCleanupFunction(function* () {
--- a/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
+++ b/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
@@ -1,15 +1,18 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"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.";
const TEST_FILE = "test-network-request.html";
const TEST_PATH = "http://example.com/browser/devtools/client/webconsole/" +
"test/" + TEST_FILE;
@@ -71,9 +74,11 @@ function* testNetmonitor(toolbox) {
yield waitUntil(() => store.getState().requests.requests.size > 0);
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);
}
--- a/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js
+++ b/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js
@@ -1,34 +1,41 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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";
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");
});
const hud = yield loadPageAndGetHud(TEST_NETWORK_REQUEST_URI);
let request = yield finishedRequest;
yield hud.ui.openNetworkPanel(request.actor);
let toolbox = gDevTools.getToolbox(hud.target);
is(toolbox.currentToolId, "netmonitor", "Network panel was opened");
- let panel = toolbox.getCurrentPanel();
+ let monitor = toolbox.getCurrentPanel();
- let { store, windowRequire } = panel.panelWin;
+ let { store, windowRequire } = monitor.panelWin;
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);
});
--- a/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
+++ b/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
@@ -1,23 +1,28 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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.
"use strict";
const TEST_FILE_URI =
"http://example.com/browser/devtools/client/webconsole/test/" +
"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* () {
let requests = [];
let { browser } = yield loadTab(TEST_URI);
yield pushPrefEnv();
hud = yield openConsole();
@@ -32,18 +37,18 @@ add_task(function* () {
yield testMessages();
let htmlRequest = requests.find(e => e.request.url.endsWith("html"));
ok(htmlRequest, "htmlRequest was a html");
yield hud.ui.openNetworkPanel(htmlRequest.actor);
let toolbox = gDevTools.getToolbox(hud.target);
is(toolbox.currentToolId, "netmonitor", "Network panel was opened");
- let panel = toolbox.getCurrentPanel();
- let { store, windowRequire } = panel.panelWin;
+ let monitor = toolbox.getCurrentPanel();
+ let { store, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let { getSelectedRequest } = windowRequire("devtools/client/netmonitor/src/selectors/index");
let selected = getSelectedRequest(store.getState());
is(selected.method, htmlRequest.request.method,
"The correct request is selected");
is(selected.url, htmlRequest.request.url,
"The correct request is definitely selected");
@@ -59,16 +64,18 @@ add_task(function* () {
is(selected.method, htmlRequest.request.method,
"The correct request is selected");
is(selected.url, htmlRequest.request.url,
"The correct request is definitely selected");
// All tests are done. Shutdown.
HUDService.lastFinishedRequest.callback = null;
htmlRequest = browser = requests = hud = null;
+
+ yield waitForExistingRequests(monitor);
});
function testMessages() {
return waitForMessages({
webconsole: hud,
messages: [{
text: "running network console logging tests",
category: CATEGORY_WEBDEV,