--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -129,50 +129,38 @@ class FirefoxDataProvider {
async fetchResponseContent(responseContent) {
let payload = {};
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;
}
async fetchRequestHeaders(requestHeaders) {
let payload = {};
if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
let headers = await fetchHeaders(requestHeaders, this.getLongString);
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;
}
async fetchResponseHeaders(responseHeaders) {
let payload = {};
if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
let headers = await fetchHeaders(responseHeaders, this.getLongString);
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;
}
async fetchPostData(requestPostData) {
let payload = {};
if (requestPostData && requestPostData.postData) {
let { text } = requestPostData.postData;
@@ -183,20 +171,16 @@ class FirefoxDataProvider {
// two new-line characters at the end.
const headersSize = headers.reduce((acc, { name, value }) => {
return acc + name.length + value.length + 2;
}, 0);
requestPostData.postData.text = postData;
payload.requestPostData = Object.assign({}, requestPostData);
payload.requestHeadersFromUploadStream = { headers, headersSize };
-
- // Lock down requestPostDataAvailable once we fetch data from back-end.
- // Using this as flag to prevent fetching arrived data again.
- payload.requestPostDataAvailable = false;
}
return payload;
}
async fetchRequestCookies(requestCookies) {
let payload = {};
if (requestCookies) {
let reqCookies = [];
@@ -209,20 +193,16 @@ class FirefoxDataProvider {
reqCookies.push(Object.assign({}, cookie, {
value: await this.getLongString(cookie.value),
}));
}
if (reqCookies.length) {
payload.requestCookies = reqCookies;
}
}
-
- // Lock down requestCookiesAvailable once we fetch data from back-end.
- // Using this as flag to prevent fetching arrived data again.
- payload.requestCookiesAvailable = false;
}
return payload;
}
async fetchResponseCookies(responseCookies) {
let payload = {};
if (responseCookies) {
let resCookies = [];
@@ -235,20 +215,16 @@ class FirefoxDataProvider {
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;
}
/**
* Public API used by the Toolbox: Tells if there is still any pending request.
*
* @return {boolean} returns true if the payload queue is empty
@@ -429,17 +405,22 @@ class FirefoxDataProvider {
}
// Fetch the data
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);
if (this.actions.updateRequest) {
- await this.actions.updateRequest(actor, payload, true);
+ await this.actions.updateRequest(actor, {
+ ...payload,
+ // Lockdown *Available property once we fetch data from back-end.
+ // Using this as a flag to prevent fetching arrived data again.
+ [`${method}Available`]: false,
+ }, true);
}
return payload;
});
this.lazyRequestData.set(key, promise);
return promise;
--- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
@@ -35,16 +35,17 @@ class RequestListContextMenu {
mimeType,
httpVersion,
requestHeaders,
requestHeadersAvailable,
requestPostData,
requestPostDataAvailable,
responseHeaders,
responseHeadersAvailable,
+ responseContent,
responseContentAvailable,
url,
} = selectedRequest;
let {
cloneSelectedRequest,
openStatistics,
} = this.props;
let menu = [];
@@ -68,27 +69,30 @@ class RequestListContextMenu {
copySubmenu.push({
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),
+ click: () => this.copyPostData(id, formDataSections, requestPostData),
});
copySubmenu.push({
id: "request-list-context-copy-as-curl",
label: L10N.getStr("netmonitor.context.copyAsCurl"),
accesskey: L10N.getStr("netmonitor.context.copyAsCurl.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 && (requestHeadersAvailable || requestHeaders)),
- click: () => this.copyAsCurl(id, url, method, requestHeaders, httpVersion),
+ visible: !!(selectedRequest &&
+ (requestHeadersAvailable || requestHeaders) &&
+ (responseContentAvailable || responseContent)),
+ click: () =>
+ this.copyAsCurl(id, url, method, httpVersion, requestHeaders, responseContent),
});
copySubmenu.push({
type: "separator",
visible: copySubmenu.slice(0, 4).some((subMenu) => subMenu.visible),
});
copySubmenu.push({
@@ -112,26 +116,27 @@ class RequestListContextMenu {
});
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),
+ visible: !!(selectedRequest && (responseContentAvailable || responseContent)),
+ click: () => this.copyResponse(id, responseContent),
});
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 && mimeType && mimeType.includes("image/")),
- click: () => this.copyImageAsDataUri(id, mimeType),
+ visible: !!(selectedRequest && (responseContentAvailable || responseContent) &&
+ mimeType && mimeType.includes("image/")),
+ click: () => this.copyImageAsDataUri(id, mimeType, responseContent),
});
copySubmenu.push({
type: "separator",
visible: copySubmenu.slice(5, 9).some((subMenu) => subMenu.visible),
});
copySubmenu.push({
@@ -156,18 +161,19 @@ class RequestListContextMenu {
visible: sortedRequests.size > 0,
click: () => this.saveAllAsHar(sortedRequests),
});
menu.push({
id: "request-list-context-save-image-as",
label: L10N.getStr("netmonitor.context.saveImageAs"),
accesskey: L10N.getStr("netmonitor.context.saveImageAs.accesskey"),
- visible: !!(selectedRequest && mimeType && mimeType.includes("image/")),
- click: () => this.saveImageAs(id, url),
+ visible: !!(selectedRequest && (responseContentAvailable || responseContent) &&
+ mimeType && mimeType.includes("image/")),
+ click: () => this.saveImageAs(id, url, responseContent),
});
menu.push({
type: "separator",
visible: copySubmenu.slice(10, 14).some((subMenu) => subMenu.visible),
});
menu.push({
@@ -258,106 +264,112 @@ class RequestListContextMenu {
let params = getUrlQuery(url).split("&");
copyString(params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n"));
}
/**
* Copy the request form data parameters (or raw payload) from
* the currently selected item.
*/
- async copyPostData(id, formDataSections) {
+ async copyPostData(id, formDataSections, requestPostData) {
let params = [];
// Try to extract any form data parameters.
formDataSections.forEach(section => {
let paramsArray = parseQueryString(section);
if (paramsArray) {
params = [...params, ...paramsArray];
}
});
let string = params
.map(param => param.name + (param.value ? "=" + param.value : ""))
.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
// Fall back to raw payload.
if (!string) {
- let { requestPostData } = await this.props.connector
- .requestData(id, "requestPostData");
+ requestPostData = requestPostData ||
+ await this.props.connector.requestData(id, "requestPostData").requestPostData;
+
string = requestPostData.postData.text;
if (Services.appinfo.OS !== "WINNT") {
string = string.replace(/\r/g, "");
}
}
copyString(string);
}
/**
* 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");
+ async copyAsCurl(id, url, method, httpVersion, requestHeaders, requestPostData) {
+ requestHeaders = requestHeaders ||
+ await this.props.connector.requestData(id, "requestHeaders");
+
+ requestPostData = requestPostData ||
+ await this.props.connector.requestData(id, "requestPostData").requestPostData;
+
// Create a sanitized object for the Curl command generator.
let data = {
url,
method,
headers: requestHeaders.headers,
- httpVersion: httpVersion,
+ httpVersion,
postDataText: requestPostData ? requestPostData.postData.text : "",
};
copyString(Curl.generateCommand(data));
}
/**
* Copy the raw request headers from the currently selected item.
*/
async copyRequestHeaders(id, requestHeaders) {
- if (!requestHeaders) {
- requestHeaders = await this.props.connector.requestData(id, "requestHeaders");
- }
+ requestHeaders = requestHeaders ||
+ await this.props.connector.requestData(id, "requestHeaders");
+
let rawHeaders = requestHeaders.rawHeaders.trim();
if (Services.appinfo.OS !== "WINNT") {
rawHeaders = rawHeaders.replace(/\r/g, "");
}
copyString(rawHeaders);
}
/**
* Copy the raw response headers from the currently selected item.
*/
async copyResponseHeaders(id, responseHeaders) {
- if (!responseHeaders) {
- responseHeaders = await this.props.connector.requestData(id, "responseHeaders");
- }
+ responseHeaders = responseHeaders ||
+ await this.props.connector.requestData(id, "responseHeaders");
+
let rawHeaders = responseHeaders.rawHeaders.trim();
if (Services.appinfo.OS !== "WINNT") {
rawHeaders = rawHeaders.replace(/\r/g, "");
}
copyString(rawHeaders);
}
/**
* Copy image as data uri.
*/
- async copyImageAsDataUri(id, mimeType) {
- let responseContent = await this.props.connector.requestData(id, "responseContent");
+ async copyImageAsDataUri(id, mimeType, responseContent) {
+ responseContent = responseContent ||
+ await this.props.connector.requestData(id, "responseContent");
+
let { encoding, text } = responseContent.content;
copyString(formDataURI(mimeType, encoding, text));
}
/**
* Save image as.
*/
- async saveImageAs(id, url) {
- let responseContent = await this.props.connector.requestData(id, "responseContent");
+ async saveImageAs(id, url, responseContent) {
+ responseContent = responseContent ||
+ await this.props.connector.requestData(id, "responseContent");
+
let { encoding, text } = responseContent.content;
let fileName = getUrlBaseName(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);
@@ -366,18 +378,20 @@ class RequestListContextMenu {
data = text;
}
saveAs(new Blob([data]), fileName, document);
}
/**
* Copy response data as a string.
*/
- async copyResponse(id) {
- let responseContent = await this.props.connector.requestData(id, "responseContent");
+ async copyResponse(id, responseContent) {
+ responseContent = responseContent ||
+ await this.props.connector.requestData(id, "responseContent");
+
copyString(responseContent.content.text);
}
/**
* Copy HAR from the network panel content to the clipboard.
*/
copyAllAsHar(sortedRequests) {
return HarExporter.copy(this.getDefaultHarOptions(sortedRequests));