mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 20:35:50 +00:00
Bug 1362036 - Implement http inspection in new console; r=nchevobbe
MozReview-Commit-ID: FhYePLM2T3O --HG-- extra : rebase_source : 79f3cd0d99aee930b97ac1b0323e62bd9b6aa732
This commit is contained in:
parent
eb2fe0e37f
commit
bc02e69656
@ -203,11 +203,11 @@ const HeadersPanel = createClass({
|
||||
className: "headers-summary learn-more-link",
|
||||
}),
|
||||
button({
|
||||
className: "devtools-button",
|
||||
className: "devtools-button edit-and-resend-button",
|
||||
onClick: cloneSelectedRequest,
|
||||
}, EDIT_AND_RESEND),
|
||||
button({
|
||||
className: "devtools-button",
|
||||
className: "devtools-button raw-headers-button",
|
||||
onClick: this.toggleRawHeaders,
|
||||
}, RAW_HEADERS),
|
||||
)
|
||||
|
@ -11,7 +11,7 @@ const {
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const Actions = require("../actions/index");
|
||||
const { L10N } = require("../utils/l10n");
|
||||
const { getSelectedRequest } = require("../selectors/index");
|
||||
const { PANELS } = require("../constants");
|
||||
|
||||
// Components
|
||||
const Tabbar = createFactory(require("devtools/client/shared/components/tabs/tabbar"));
|
||||
@ -56,45 +56,45 @@ function TabboxPanel({
|
||||
showAllTabsMenu: true,
|
||||
},
|
||||
TabPanel({
|
||||
id: "headers",
|
||||
id: PANELS.HEADERS,
|
||||
title: HEADERS_TITLE,
|
||||
},
|
||||
HeadersPanel({ request, cloneSelectedRequest }),
|
||||
),
|
||||
TabPanel({
|
||||
id: "cookies",
|
||||
id: PANELS.COOKIES,
|
||||
title: COOKIES_TITLE,
|
||||
},
|
||||
CookiesPanel({ request }),
|
||||
),
|
||||
TabPanel({
|
||||
id: "params",
|
||||
id: PANELS.PARAMS,
|
||||
title: PARAMS_TITLE,
|
||||
},
|
||||
ParamsPanel({ request }),
|
||||
),
|
||||
TabPanel({
|
||||
id: "response",
|
||||
id: PANELS.RESPONSE,
|
||||
title: RESPONSE_TITLE,
|
||||
},
|
||||
ResponsePanel({ request }),
|
||||
),
|
||||
TabPanel({
|
||||
id: "timings",
|
||||
id: PANELS.TIMINGS,
|
||||
title: TIMINGS_TITLE,
|
||||
},
|
||||
TimingsPanel({ request }),
|
||||
),
|
||||
request.cause && request.cause.stacktrace && request.cause.stacktrace.length > 0 &&
|
||||
TabPanel({
|
||||
id: "stack-trace",
|
||||
id: PANELS.STACK_TRACE,
|
||||
title: STACK_TRACE_TITLE,
|
||||
},
|
||||
StackTracePanel({ request, sourceMapService }),
|
||||
),
|
||||
request.securityState && request.securityState !== "insecure" &&
|
||||
TabPanel({
|
||||
id: "security",
|
||||
id: PANELS.SECURITY,
|
||||
title: SECURITY_TITLE,
|
||||
},
|
||||
SecurityPanel({ request }),
|
||||
@ -107,7 +107,7 @@ TabboxPanel.displayName = "TabboxPanel";
|
||||
|
||||
TabboxPanel.propTypes = {
|
||||
activeTabId: PropTypes.string,
|
||||
cloneSelectedRequest: PropTypes.func.isRequired,
|
||||
cloneSelectedRequest: PropTypes.func,
|
||||
request: PropTypes.object,
|
||||
selectTab: PropTypes.func.isRequired,
|
||||
// Service to enable the source map feature.
|
||||
@ -116,8 +116,6 @@ TabboxPanel.propTypes = {
|
||||
|
||||
module.exports = connect(
|
||||
(state) => ({
|
||||
activeTabId: state.ui.detailsPanelSelectedTab,
|
||||
request: getSelectedRequest(state),
|
||||
}),
|
||||
(dispatch) => ({
|
||||
cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
|
||||
|
@ -5,54 +5,29 @@
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { CurlUtils } = require("devtools/client/shared/curl");
|
||||
const { TimelineFront } = require("devtools/shared/fronts/timeline");
|
||||
const { ACTIVITY_TYPE, EVENTS } = require("../constants");
|
||||
const { getDisplayedRequestById } = require("../selectors/index");
|
||||
const { fetchHeaders, formDataURI } = require("../utils/request-utils");
|
||||
const FirefoxDataProvider = require("./firefox-data-provider");
|
||||
|
||||
class FirefoxConnector {
|
||||
constructor() {
|
||||
// Internal properties
|
||||
this.payloadQueue = [];
|
||||
|
||||
// Public methods
|
||||
this.connect = this.connect.bind(this);
|
||||
this.disconnect = this.disconnect.bind(this);
|
||||
this.willNavigate = this.willNavigate.bind(this);
|
||||
this.displayCachedEvents = this.displayCachedEvents.bind(this);
|
||||
this.onDocLoadingMarker = this.onDocLoadingMarker.bind(this);
|
||||
this.addRequest = this.addRequest.bind(this);
|
||||
this.updateRequest = this.updateRequest.bind(this);
|
||||
this.fetchImage = this.fetchImage.bind(this);
|
||||
this.fetchRequestHeaders = this.fetchRequestHeaders.bind(this);
|
||||
this.fetchResponseHeaders = this.fetchResponseHeaders.bind(this);
|
||||
this.fetchPostData = this.fetchPostData.bind(this);
|
||||
this.fetchResponseCookies = this.fetchResponseCookies.bind(this);
|
||||
this.fetchRequestCookies = this.fetchRequestCookies.bind(this);
|
||||
this.getPayloadFromQueue = this.getPayloadFromQueue.bind(this);
|
||||
this.isQueuePayloadReady = this.isQueuePayloadReady.bind(this);
|
||||
this.pushPayloadToQueue = this.pushPayloadToQueue.bind(this);
|
||||
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
|
||||
this.setPreferences = this.setPreferences.bind(this);
|
||||
this.triggerActivity = this.triggerActivity.bind(this);
|
||||
this.inspectRequest = this.inspectRequest.bind(this);
|
||||
this.getLongString = this.getLongString.bind(this);
|
||||
this.getNetworkRequest = this.getNetworkRequest.bind(this);
|
||||
this.getTabTarget = this.getTabTarget.bind(this);
|
||||
this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
|
||||
|
||||
// Event handlers
|
||||
this.onNetworkEvent = this.onNetworkEvent.bind(this);
|
||||
this.onNetworkEventUpdate = this.onNetworkEventUpdate.bind(this);
|
||||
this.onRequestHeaders = this.onRequestHeaders.bind(this);
|
||||
this.onRequestCookies = this.onRequestCookies.bind(this);
|
||||
this.onRequestPostData = this.onRequestPostData.bind(this);
|
||||
this.onSecurityInfo = this.onSecurityInfo.bind(this);
|
||||
this.onResponseHeaders = this.onResponseHeaders.bind(this);
|
||||
this.onResponseCookies = this.onResponseCookies.bind(this);
|
||||
this.onResponseContent = this.onResponseContent.bind(this);
|
||||
this.onEventTimings = this.onEventTimings.bind(this);
|
||||
// Internals
|
||||
this.getLongString = this.getLongString.bind(this);
|
||||
this.getNetworkRequest = this.getNetworkRequest.bind(this);
|
||||
}
|
||||
|
||||
async connect(connection, actions, getState) {
|
||||
@ -63,10 +38,17 @@ class FirefoxConnector {
|
||||
|
||||
this.webConsoleClient = this.tabTarget.activeConsole;
|
||||
|
||||
this.dataProvider = new FirefoxDataProvider({
|
||||
webConsoleClient: this.webConsoleClient,
|
||||
actions: this.actions,
|
||||
});
|
||||
|
||||
this.tabTarget.on("will-navigate", this.willNavigate);
|
||||
this.tabTarget.on("close", this.disconnect);
|
||||
this.webConsoleClient.on("networkEvent", this.onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate", this.onNetworkEventUpdate);
|
||||
this.webConsoleClient.on("networkEvent",
|
||||
this.dataProvider.onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate",
|
||||
this.dataProvider.onNetworkEventUpdate);
|
||||
|
||||
// Don't start up waiting for timeline markers if the server isn't
|
||||
// recent enough to emit the markers we're interested in.
|
||||
@ -96,6 +78,7 @@ class FirefoxConnector {
|
||||
this.webConsoleClient.off("networkEventUpdate");
|
||||
this.webConsoleClient = null;
|
||||
this.timelineFront = null;
|
||||
this.dataProvider = null;
|
||||
}
|
||||
|
||||
willNavigate() {
|
||||
@ -114,10 +97,10 @@ class FirefoxConnector {
|
||||
displayCachedEvents() {
|
||||
for (let networkInfo of this.webConsoleClient.getNetworkEvents()) {
|
||||
// First add the request to the timeline.
|
||||
this.onNetworkEvent("networkEvent", networkInfo);
|
||||
this.dataProvider.onNetworkEvent("networkEvent", networkInfo);
|
||||
// Then replay any updates already received.
|
||||
for (let updateType of networkInfo.updates) {
|
||||
this.onNetworkEventUpdate("networkEventUpdate", {
|
||||
this.dataProvider.onNetworkEventUpdate("networkEventUpdate", {
|
||||
packet: { updateType },
|
||||
networkInfo,
|
||||
});
|
||||
@ -135,222 +118,6 @@ class FirefoxConnector {
|
||||
this.actions.addTimingMarker(marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new network request to application state.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @param {object} data data payload will be added to application state
|
||||
*/
|
||||
addRequest(id, data) {
|
||||
let {
|
||||
method,
|
||||
url,
|
||||
isXHR,
|
||||
cause,
|
||||
startedDateTime,
|
||||
fromCache,
|
||||
fromServiceWorker,
|
||||
} = data;
|
||||
|
||||
this.actions.addRequest(
|
||||
id,
|
||||
{
|
||||
// Convert the received date/time string to a unix timestamp.
|
||||
startedMillis: Date.parse(startedDateTime),
|
||||
method,
|
||||
url,
|
||||
isXHR,
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker,
|
||||
},
|
||||
true,
|
||||
)
|
||||
.then(() => window.emit(EVENTS.REQUEST_ADDED, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a network request if it already exists in application state.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @param {object} data data payload will be updated to application state
|
||||
*/
|
||||
async updateRequest(id, data) {
|
||||
let {
|
||||
mimeType,
|
||||
responseContent,
|
||||
responseCookies,
|
||||
responseHeaders,
|
||||
requestCookies,
|
||||
requestHeaders,
|
||||
requestPostData,
|
||||
} = data;
|
||||
|
||||
// fetch request detail contents in parallel
|
||||
let [
|
||||
imageObj,
|
||||
requestHeadersObj,
|
||||
responseHeadersObj,
|
||||
postDataObj,
|
||||
requestCookiesObj,
|
||||
responseCookiesObj,
|
||||
] = await Promise.all([
|
||||
this.fetchImage(mimeType, responseContent),
|
||||
this.fetchRequestHeaders(requestHeaders),
|
||||
this.fetchResponseHeaders(responseHeaders),
|
||||
this.fetchPostData(requestPostData),
|
||||
this.fetchRequestCookies(requestCookies),
|
||||
this.fetchResponseCookies(responseCookies),
|
||||
]);
|
||||
|
||||
let payload = Object.assign({}, data,
|
||||
imageObj, requestHeadersObj, responseHeadersObj,
|
||||
postDataObj, requestCookiesObj, responseCookiesObj);
|
||||
|
||||
this.pushPayloadToQueue(id, payload);
|
||||
|
||||
if (this.isQueuePayloadReady(id)) {
|
||||
await this.actions.updateRequest(id, this.getPayloadFromQueue(id).payload, true);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchImage(mimeType, responseContent) {
|
||||
let payload = {};
|
||||
if (mimeType && responseContent && responseContent.content) {
|
||||
let { encoding, text } = responseContent.content;
|
||||
let response = await this.getLongString(text);
|
||||
|
||||
if (mimeType.includes("image/")) {
|
||||
payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
|
||||
}
|
||||
|
||||
responseContent.content.text = response;
|
||||
payload.responseContent = responseContent;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async fetchPostData(requestPostData) {
|
||||
let payload = {};
|
||||
if (requestPostData && requestPostData.postData) {
|
||||
let { text } = requestPostData.postData;
|
||||
let postData = await this.getLongString(text);
|
||||
const headers = CurlUtils.getHeadersFromMultipartText(postData);
|
||||
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 };
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async fetchRequestCookies(requestCookies) {
|
||||
let payload = {};
|
||||
if (requestCookies) {
|
||||
let reqCookies = [];
|
||||
// request store cookies in requestCookies or requestCookies.cookies
|
||||
let cookies = requestCookies.cookies ?
|
||||
requestCookies.cookies : requestCookies;
|
||||
// make sure cookies is iterable
|
||||
if (typeof cookies[Symbol.iterator] === "function") {
|
||||
for (let cookie of cookies) {
|
||||
reqCookies.push(Object.assign({}, cookie, {
|
||||
value: await this.getLongString(cookie.value),
|
||||
}));
|
||||
}
|
||||
if (reqCookies.length) {
|
||||
payload.requestCookies = reqCookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access a payload item from payload queue.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @return {boolean} return a queued payload item from queue.
|
||||
*/
|
||||
getPayloadFromQueue(id) {
|
||||
return this.payloadQueue.find((item) => item.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Packet order of "networkUpdateEvent" is predictable, as a result we can wait for
|
||||
* the last one "eventTimings" packet arrives to check payload is ready.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @return {boolean} return whether a specific networkEvent has been updated completely.
|
||||
*/
|
||||
isQueuePayloadReady(id) {
|
||||
let queuedPayload = this.getPayloadFromQueue(id);
|
||||
return queuedPayload && queuedPayload.payload.eventTimings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a request payload into a queue if request doesn't exist. Otherwise update the
|
||||
* request itself.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @param {object} payload request data payload
|
||||
*/
|
||||
pushPayloadToQueue(id, payload) {
|
||||
let queuedPayload = this.getPayloadFromQueue(id);
|
||||
if (!queuedPayload) {
|
||||
this.payloadQueue.push({ id, payload });
|
||||
} else {
|
||||
// Merge upcoming networkEventUpdate payload into existing one
|
||||
queuedPayload.payload = Object.assign({}, queuedPayload.payload, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a HTTP request data payload
|
||||
*
|
||||
@ -490,7 +257,7 @@ class FirefoxConnector {
|
||||
* @return {object} networkInfo data packet
|
||||
*/
|
||||
getNetworkRequest(id) {
|
||||
return this.webConsoleClient.getNetworkRequest(id);
|
||||
return this.dataProvider.getNetworkRequest(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -505,7 +272,7 @@ class FirefoxConnector {
|
||||
* are available, or rejected if something goes wrong.
|
||||
*/
|
||||
getLongString(stringGrip) {
|
||||
return this.webConsoleClient.getString(stringGrip);
|
||||
return this.dataProvider.getLongString(stringGrip);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -526,213 +293,6 @@ class FirefoxConnector {
|
||||
this.toolbox.viewSourceInDebugger(sourceURL, sourceLine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "networkEvent" message type handler.
|
||||
*
|
||||
* @param {string} type message type
|
||||
* @param {object} networkInfo network request information
|
||||
*/
|
||||
onNetworkEvent(type, networkInfo) {
|
||||
let {
|
||||
actor,
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker,
|
||||
isXHR,
|
||||
request: {
|
||||
method,
|
||||
url,
|
||||
},
|
||||
startedDateTime,
|
||||
} = networkInfo;
|
||||
|
||||
this.addRequest(actor, {
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker,
|
||||
isXHR,
|
||||
method,
|
||||
startedDateTime,
|
||||
url,
|
||||
});
|
||||
|
||||
window.emit(EVENTS.NETWORK_EVENT, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* The "networkEventUpdate" message type handler.
|
||||
*
|
||||
* @param {string} type message type
|
||||
* @param {object} packet the message received from the server.
|
||||
* @param {object} networkInfo the network request information.
|
||||
*/
|
||||
onNetworkEventUpdate(type, { packet, networkInfo }) {
|
||||
let { actor } = networkInfo;
|
||||
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
this.webConsoleClient.getRequestHeaders(actor, this.onRequestHeaders);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
|
||||
break;
|
||||
case "requestCookies":
|
||||
this.webConsoleClient.getRequestCookies(actor, this.onRequestCookies);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
|
||||
break;
|
||||
case "requestPostData":
|
||||
this.webConsoleClient.getRequestPostData(actor, this.onRequestPostData);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
|
||||
break;
|
||||
case "securityInfo":
|
||||
this.updateRequest(actor, {
|
||||
securityState: networkInfo.securityInfo,
|
||||
}).then(() => {
|
||||
this.webConsoleClient.getSecurityInfo(actor, this.onSecurityInfo);
|
||||
window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
|
||||
});
|
||||
break;
|
||||
case "responseHeaders":
|
||||
this.webConsoleClient.getResponseHeaders(actor, this.onResponseHeaders);
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
|
||||
break;
|
||||
case "responseCookies":
|
||||
this.webConsoleClient.getResponseCookies(actor, this.onResponseCookies);
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
|
||||
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(() => {
|
||||
window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
|
||||
});
|
||||
break;
|
||||
case "responseContent":
|
||||
this.webConsoleClient.getResponseContent(actor,
|
||||
this.onResponseContent.bind(this, {
|
||||
contentSize: networkInfo.response.bodySize,
|
||||
transferredSize: networkInfo.response.transferredSize,
|
||||
mimeType: networkInfo.response.content.mimeType
|
||||
}));
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
|
||||
break;
|
||||
case "eventTimings":
|
||||
this.updateRequest(actor, { totalTime: networkInfo.totalTime })
|
||||
.then(() => {
|
||||
this.webConsoleClient.getEventTimings(actor, this.onEventTimings);
|
||||
window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestHeaders" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onRequestHeaders(response) {
|
||||
this.updateRequest(response.from, {
|
||||
requestHeaders: response
|
||||
}).then(() => {
|
||||
window.emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestCookies" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onRequestCookies(response) {
|
||||
this.updateRequest(response.from, {
|
||||
requestCookies: response
|
||||
}).then(() => {
|
||||
window.emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestPostData" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onRequestPostData(response) {
|
||||
this.updateRequest(response.from, {
|
||||
requestPostData: response
|
||||
}).then(() => {
|
||||
window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "securityInfo" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onSecurityInfo(response) {
|
||||
this.updateRequest(response.from, {
|
||||
securityInfo: response.securityInfo
|
||||
}).then(() => {
|
||||
window.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) {
|
||||
this.updateRequest(response.from, {
|
||||
responseHeaders: response
|
||||
}).then(() => {
|
||||
window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseCookies" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onResponseCookies(response) {
|
||||
this.updateRequest(response.from, {
|
||||
responseCookies: response
|
||||
}).then(() => {
|
||||
window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseContent" packet.
|
||||
*
|
||||
* @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);
|
||||
this.updateRequest(response.from, payload).then(() => {
|
||||
window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "eventTimings" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onEventTimings(response) {
|
||||
this.updateRequest(response.from, {
|
||||
eventTimings: response
|
||||
}).then(() => {
|
||||
window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FirefoxConnector();
|
||||
|
@ -0,0 +1,525 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* eslint-disable block-scoped-var */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { EVENTS } = require("../constants");
|
||||
const { CurlUtils } = require("devtools/client/shared/curl");
|
||||
const { fetchHeaders, formDataURI } = require("../utils/request-utils");
|
||||
|
||||
/**
|
||||
* This object is responsible for fetching additional HTTP
|
||||
* data from the backend.
|
||||
*/
|
||||
class FirefoxDataProvider {
|
||||
constructor({webConsoleClient, actions}) {
|
||||
// Options
|
||||
this.webConsoleClient = webConsoleClient;
|
||||
this.actions = actions;
|
||||
|
||||
// Internal properties
|
||||
this.payloadQueue = [];
|
||||
|
||||
// Public methods
|
||||
this.addRequest = this.addRequest.bind(this);
|
||||
this.updateRequest = this.updateRequest.bind(this);
|
||||
|
||||
// Internals
|
||||
this.fetchImage = this.fetchImage.bind(this);
|
||||
this.fetchRequestHeaders = this.fetchRequestHeaders.bind(this);
|
||||
this.fetchResponseHeaders = this.fetchResponseHeaders.bind(this);
|
||||
this.fetchPostData = this.fetchPostData.bind(this);
|
||||
this.fetchResponseCookies = this.fetchResponseCookies.bind(this);
|
||||
this.fetchRequestCookies = this.fetchRequestCookies.bind(this);
|
||||
this.getPayloadFromQueue = this.getPayloadFromQueue.bind(this);
|
||||
this.isQueuePayloadReady = this.isQueuePayloadReady.bind(this);
|
||||
this.pushPayloadToQueue = this.pushPayloadToQueue.bind(this);
|
||||
this.getLongString = this.getLongString.bind(this);
|
||||
this.getNetworkRequest = this.getNetworkRequest.bind(this);
|
||||
|
||||
// Event handlers
|
||||
this.onNetworkEvent = this.onNetworkEvent.bind(this);
|
||||
this.onNetworkEventUpdate = this.onNetworkEventUpdate.bind(this);
|
||||
this.onRequestHeaders = this.onRequestHeaders.bind(this);
|
||||
this.onRequestCookies = this.onRequestCookies.bind(this);
|
||||
this.onRequestPostData = this.onRequestPostData.bind(this);
|
||||
this.onSecurityInfo = this.onSecurityInfo.bind(this);
|
||||
this.onResponseHeaders = this.onResponseHeaders.bind(this);
|
||||
this.onResponseCookies = this.onResponseCookies.bind(this);
|
||||
this.onResponseContent = this.onResponseContent.bind(this);
|
||||
this.onEventTimings = this.onEventTimings.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new network request to application state.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @param {object} data data payload will be added to application state
|
||||
*/
|
||||
async addRequest(id, data) {
|
||||
let {
|
||||
method,
|
||||
url,
|
||||
isXHR,
|
||||
cause,
|
||||
startedDateTime,
|
||||
fromCache,
|
||||
fromServiceWorker,
|
||||
} = data;
|
||||
|
||||
if (this.actions.addRequest) {
|
||||
await this.actions.addRequest(id, {
|
||||
// Convert the received date/time string to a unix timestamp.
|
||||
startedMillis: Date.parse(startedDateTime),
|
||||
method,
|
||||
url,
|
||||
isXHR,
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker},
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
emit(EVENTS.REQUEST_ADDED, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a network request if it already exists in application state.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @param {object} data data payload will be updated to application state
|
||||
*/
|
||||
async updateRequest(id, data) {
|
||||
let {
|
||||
mimeType,
|
||||
responseContent,
|
||||
responseCookies,
|
||||
responseHeaders,
|
||||
requestCookies,
|
||||
requestHeaders,
|
||||
requestPostData,
|
||||
} = data;
|
||||
|
||||
// fetch request detail contents in parallel
|
||||
let [
|
||||
imageObj,
|
||||
requestHeadersObj,
|
||||
responseHeadersObj,
|
||||
postDataObj,
|
||||
requestCookiesObj,
|
||||
responseCookiesObj,
|
||||
] = await Promise.all([
|
||||
this.fetchImage(mimeType, responseContent),
|
||||
this.fetchRequestHeaders(requestHeaders),
|
||||
this.fetchResponseHeaders(responseHeaders),
|
||||
this.fetchPostData(requestPostData),
|
||||
this.fetchRequestCookies(requestCookies),
|
||||
this.fetchResponseCookies(responseCookies),
|
||||
]);
|
||||
|
||||
let payload = Object.assign({},
|
||||
data,
|
||||
imageObj,
|
||||
requestHeadersObj,
|
||||
responseHeadersObj,
|
||||
postDataObj,
|
||||
requestCookiesObj,
|
||||
responseCookiesObj
|
||||
);
|
||||
|
||||
this.pushPayloadToQueue(id, payload);
|
||||
|
||||
if (this.actions.updateRequest && this.isQueuePayloadReady(id)) {
|
||||
await this.actions.updateRequest(id, this.getPayloadFromQueue(id).payload, true);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchImage(mimeType, responseContent) {
|
||||
let payload = {};
|
||||
if (mimeType && responseContent && responseContent.content) {
|
||||
let { encoding, text } = responseContent.content;
|
||||
let response = await this.getLongString(text);
|
||||
|
||||
if (mimeType.includes("image/")) {
|
||||
payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
|
||||
}
|
||||
|
||||
responseContent.content.text = response;
|
||||
payload.responseContent = responseContent;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async fetchPostData(requestPostData) {
|
||||
let payload = {};
|
||||
if (requestPostData && requestPostData.postData) {
|
||||
let { text } = requestPostData.postData;
|
||||
let postData = await this.getLongString(text);
|
||||
const headers = CurlUtils.getHeadersFromMultipartText(postData);
|
||||
|
||||
// Calculate total header size and don't forget to include
|
||||
// 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 };
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async fetchRequestCookies(requestCookies) {
|
||||
let payload = {};
|
||||
if (requestCookies) {
|
||||
let reqCookies = [];
|
||||
// request store cookies in requestCookies or requestCookies.cookies
|
||||
let cookies = requestCookies.cookies ?
|
||||
requestCookies.cookies : requestCookies;
|
||||
// make sure cookies is iterable
|
||||
if (typeof cookies[Symbol.iterator] === "function") {
|
||||
for (let cookie of cookies) {
|
||||
reqCookies.push(Object.assign({}, cookie, {
|
||||
value: await this.getLongString(cookie.value),
|
||||
}));
|
||||
}
|
||||
if (reqCookies.length) {
|
||||
payload.requestCookies = reqCookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access a payload item from payload queue.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @return {boolean} return a queued payload item from queue.
|
||||
*/
|
||||
getPayloadFromQueue(id) {
|
||||
return this.payloadQueue.find((item) => item.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
isQueuePayloadReady(id) {
|
||||
let queuedPayload = this.getPayloadFromQueue(id);
|
||||
|
||||
// TODO we should find a better solution since it might happen
|
||||
// that eventTimings is not the last update.
|
||||
return queuedPayload && queuedPayload.payload.eventTimings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a request payload into a queue if request doesn't exist. Otherwise update the
|
||||
* request itself.
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @param {object} payload request data payload
|
||||
*/
|
||||
pushPayloadToQueue(id, payload) {
|
||||
let queuedPayload = this.getPayloadFromQueue(id);
|
||||
if (!queuedPayload) {
|
||||
this.payloadQueue.push({ id, payload });
|
||||
} else {
|
||||
// Merge upcoming networkEventUpdate payload into existing one
|
||||
queuedPayload.payload = Object.assign({}, queuedPayload.payload, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the network information packet from actor server
|
||||
*
|
||||
* @param {string} id request id
|
||||
* @return {object} networkInfo data packet
|
||||
*/
|
||||
getNetworkRequest(id) {
|
||||
return this.webConsoleClient.getNetworkRequest(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the full text of a LongString.
|
||||
*
|
||||
* @param {object|string} stringGrip
|
||||
* The long string grip containing the corresponding actor.
|
||||
* If you pass in a plain string (by accident or because you're lazy),
|
||||
* then a promise of the same string is simply returned.
|
||||
* @return {object}
|
||||
* A promise that is resolved when the full string contents
|
||||
* are available, or rejected if something goes wrong.
|
||||
*/
|
||||
getLongString(stringGrip) {
|
||||
return this.webConsoleClient.getString(stringGrip);
|
||||
}
|
||||
|
||||
/**
|
||||
* The "networkEvent" message type handler.
|
||||
*
|
||||
* @param {string} type message type
|
||||
* @param {object} networkInfo network request information
|
||||
*/
|
||||
onNetworkEvent(type, networkInfo) {
|
||||
let {
|
||||
actor,
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker,
|
||||
isXHR,
|
||||
request: {
|
||||
method,
|
||||
url,
|
||||
},
|
||||
startedDateTime,
|
||||
} = networkInfo;
|
||||
|
||||
this.addRequest(actor, {
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker,
|
||||
isXHR,
|
||||
method,
|
||||
startedDateTime,
|
||||
url,
|
||||
});
|
||||
|
||||
emit(EVENTS.NETWORK_EVENT, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* The "networkEventUpdate" message type handler.
|
||||
*
|
||||
* @param {string} type message type
|
||||
* @param {object} packet the message received from the server.
|
||||
* @param {object} networkInfo the network request information.
|
||||
*/
|
||||
onNetworkEventUpdate(type, { packet, networkInfo }) {
|
||||
let { actor } = networkInfo;
|
||||
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
this.webConsoleClient.getRequestHeaders(actor, this.onRequestHeaders);
|
||||
emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
|
||||
break;
|
||||
case "requestCookies":
|
||||
this.webConsoleClient.getRequestCookies(actor, this.onRequestCookies);
|
||||
emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
|
||||
break;
|
||||
case "requestPostData":
|
||||
this.webConsoleClient.getRequestPostData(actor, this.onRequestPostData);
|
||||
emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
|
||||
break;
|
||||
case "securityInfo":
|
||||
this.updateRequest(actor, {
|
||||
securityState: networkInfo.securityInfo,
|
||||
}).then(() => {
|
||||
this.webConsoleClient.getSecurityInfo(actor, this.onSecurityInfo);
|
||||
emit(EVENTS.UPDATING_SECURITY_INFO, actor);
|
||||
});
|
||||
break;
|
||||
case "responseHeaders":
|
||||
this.webConsoleClient.getResponseHeaders(actor, this.onResponseHeaders);
|
||||
emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
|
||||
break;
|
||||
case "responseCookies":
|
||||
this.webConsoleClient.getResponseCookies(actor, this.onResponseCookies);
|
||||
emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
|
||||
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.webConsoleClient.getResponseContent(actor,
|
||||
this.onResponseContent.bind(this, {
|
||||
contentSize: networkInfo.response.bodySize,
|
||||
transferredSize: networkInfo.response.transferredSize,
|
||||
mimeType: networkInfo.response.content.mimeType
|
||||
}));
|
||||
emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
|
||||
break;
|
||||
case "eventTimings":
|
||||
this.updateRequest(actor, { totalTime: networkInfo.totalTime })
|
||||
.then(() => {
|
||||
this.webConsoleClient.getEventTimings(actor, this.onEventTimings);
|
||||
emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestHeaders" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onRequestHeaders(response) {
|
||||
this.updateRequest(response.from, {
|
||||
requestHeaders: response
|
||||
}).then(() => {
|
||||
emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestCookies" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onRequestCookies(response) {
|
||||
this.updateRequest(response.from, {
|
||||
requestCookies: response
|
||||
}).then(() => {
|
||||
emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestPostData" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onRequestPostData(response) {
|
||||
this.updateRequest(response.from, {
|
||||
requestPostData: response
|
||||
}).then(() => {
|
||||
emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "securityInfo" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onSecurityInfo(response) {
|
||||
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) {
|
||||
this.updateRequest(response.from, {
|
||||
responseHeaders: response
|
||||
}).then(() => {
|
||||
emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseCookies" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onResponseCookies(response) {
|
||||
this.updateRequest(response.from, {
|
||||
responseCookies: response
|
||||
}).then(() => {
|
||||
emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseContent" packet.
|
||||
*
|
||||
* @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);
|
||||
this.updateRequest(response.from, payload).then(() => {
|
||||
emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "eventTimings" packet.
|
||||
*
|
||||
* @param {object} response the message received from the server.
|
||||
*/
|
||||
onEventTimings(response) {
|
||||
this.updateRequest(response.from, {
|
||||
eventTimings: response
|
||||
}).then(() => {
|
||||
emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard 'emit' to avoid exception in non-window environment.
|
||||
*/
|
||||
function emit(type, data) {
|
||||
if (typeof window != "undefined") {
|
||||
window.emit(type, data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FirefoxDataProvider;
|
@ -4,5 +4,6 @@
|
||||
|
||||
DevToolsModules(
|
||||
'firefox-connector.js',
|
||||
'firefox-data-provider.js',
|
||||
'index.js',
|
||||
)
|
||||
|
@ -94,6 +94,44 @@ const EVENTS = {
|
||||
CONNECTED: "connected",
|
||||
};
|
||||
|
||||
const UPDATE_PROPS = [
|
||||
"method",
|
||||
"url",
|
||||
"remotePort",
|
||||
"remoteAddress",
|
||||
"status",
|
||||
"statusText",
|
||||
"httpVersion",
|
||||
"securityState",
|
||||
"securityInfo",
|
||||
"mimeType",
|
||||
"contentSize",
|
||||
"transferredSize",
|
||||
"totalTime",
|
||||
"eventTimings",
|
||||
"headersSize",
|
||||
"customQueryValue",
|
||||
"requestHeaders",
|
||||
"requestHeadersFromUploadStream",
|
||||
"requestCookies",
|
||||
"requestPostData",
|
||||
"responseHeaders",
|
||||
"responseCookies",
|
||||
"responseContent",
|
||||
"responseContentDataUri",
|
||||
"formDataSections",
|
||||
];
|
||||
|
||||
const PANELS = {
|
||||
COOKIES: "cookies",
|
||||
HEADERS: "headers",
|
||||
PARAMS: "params",
|
||||
RESPONSE: "response",
|
||||
SECURITY: "security",
|
||||
STACK_TRACE: "stack-trace",
|
||||
TIMINGS: "timings",
|
||||
};
|
||||
|
||||
const RESPONSE_HEADERS = [
|
||||
"Cache-Control",
|
||||
"Connection",
|
||||
@ -246,11 +284,13 @@ const general = {
|
||||
ACTIVITY_TYPE,
|
||||
EVENTS,
|
||||
FILTER_SEARCH_DELAY: 200,
|
||||
UPDATE_PROPS,
|
||||
HEADERS,
|
||||
RESPONSE_HEADERS,
|
||||
FILTER_FLAGS,
|
||||
SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE: 51200, // 50 KB in bytes
|
||||
REQUESTS_WATERFALL,
|
||||
PANELS,
|
||||
};
|
||||
|
||||
// flatten constants
|
||||
|
@ -15,6 +15,7 @@ const {
|
||||
SELECT_REQUEST,
|
||||
SEND_CUSTOM_REQUEST,
|
||||
UPDATE_REQUEST,
|
||||
UPDATE_PROPS,
|
||||
} = require("../constants");
|
||||
|
||||
const Request = I.Record({
|
||||
@ -68,34 +69,6 @@ const Requests = I.Record({
|
||||
lastEndedMillis: -Infinity,
|
||||
});
|
||||
|
||||
const UPDATE_PROPS = [
|
||||
"method",
|
||||
"url",
|
||||
"remotePort",
|
||||
"remoteAddress",
|
||||
"status",
|
||||
"statusText",
|
||||
"httpVersion",
|
||||
"securityState",
|
||||
"securityInfo",
|
||||
"mimeType",
|
||||
"contentSize",
|
||||
"transferredSize",
|
||||
"totalTime",
|
||||
"eventTimings",
|
||||
"headersSize",
|
||||
"customQueryValue",
|
||||
"requestHeaders",
|
||||
"requestHeadersFromUploadStream",
|
||||
"requestCookies",
|
||||
"requestPostData",
|
||||
"responseHeaders",
|
||||
"responseCookies",
|
||||
"responseContent",
|
||||
"responseContentDataUri",
|
||||
"formDataSections",
|
||||
];
|
||||
|
||||
/**
|
||||
* Remove the currently selected custom request.
|
||||
*/
|
||||
|
@ -19,6 +19,7 @@ const {
|
||||
SELECT_REQUEST,
|
||||
TOGGLE_COLUMN,
|
||||
WATERFALL_RESIZE,
|
||||
PANELS,
|
||||
} = require("../constants");
|
||||
|
||||
const cols = {
|
||||
@ -51,7 +52,7 @@ const Columns = I.Record(
|
||||
|
||||
const UI = I.Record({
|
||||
columns: new Columns(),
|
||||
detailsPanelSelectedTab: "headers",
|
||||
detailsPanelSelectedTab: PANELS.HEADERS,
|
||||
networkDetailsOpen: false,
|
||||
browserCacheDisabled: Services.prefs.getBoolPref("devtools.cache.disabled"),
|
||||
statisticsOpen: false,
|
||||
|
@ -854,14 +854,43 @@ a.learn-more-link.webconsole-learn-more-link {
|
||||
background-position: -36px -36px;
|
||||
}
|
||||
|
||||
/* Network Messages */
|
||||
|
||||
.message.network .method {
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.network.message .network-info {
|
||||
display: none;
|
||||
margin-top: 8px;
|
||||
border: solid 1px var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.network.message.open .network-info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.network.message .network-info .panels {
|
||||
max-height: 250px;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
/* Hide 'Edit And Resend' button since the feature isn't
|
||||
supported in the Console panel. */
|
||||
.network.message #headers-panel .edit-and-resend-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.network.message #response-panel .treeTable {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.network .message-flex-body > .message-body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Output Wrapper */
|
||||
|
||||
.webconsole-output-wrapper .message .indent {
|
||||
display: inline-block;
|
||||
border-inline-end: solid 1px var(--theme-splitter-color);
|
||||
|
@ -15,6 +15,7 @@ const { batchActions } = require("devtools/client/shared/redux/middleware/deboun
|
||||
const {
|
||||
MESSAGE_ADD,
|
||||
NETWORK_MESSAGE_UPDATE,
|
||||
NETWORK_UPDATE_REQUEST,
|
||||
MESSAGES_CLEAR,
|
||||
MESSAGE_OPEN,
|
||||
MESSAGE_CLOSE,
|
||||
@ -94,7 +95,7 @@ function messageTableDataReceive(id, data) {
|
||||
};
|
||||
}
|
||||
|
||||
function networkMessageUpdate(packet, idGenerator = null) {
|
||||
function networkMessageUpdate(packet, idGenerator = null, response) {
|
||||
if (idGenerator == null) {
|
||||
idGenerator = defaultIdGenerator;
|
||||
}
|
||||
@ -104,6 +105,15 @@ function networkMessageUpdate(packet, idGenerator = null) {
|
||||
return {
|
||||
type: NETWORK_MESSAGE_UPDATE,
|
||||
message,
|
||||
response,
|
||||
};
|
||||
}
|
||||
|
||||
function networkUpdateRequest(id, data) {
|
||||
return {
|
||||
type: NETWORK_UPDATE_REQUEST,
|
||||
id,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
@ -179,6 +189,7 @@ module.exports = {
|
||||
messageClose,
|
||||
messageTableDataGet,
|
||||
networkMessageUpdate,
|
||||
networkUpdateRequest,
|
||||
messageObjectPropertiesLoad,
|
||||
messageObjectEntriesLoad,
|
||||
// for test purpose only.
|
||||
|
@ -67,7 +67,7 @@ const ConsoleOutput = createClass({
|
||||
const visibleMessagesDelta =
|
||||
nextProps.visibleMessages.length - this.props.visibleMessages.length;
|
||||
const messagesDelta =
|
||||
nextProps.messages.length - this.props.messages.length;
|
||||
nextProps.messages.size - this.props.messages.size;
|
||||
|
||||
// We need to scroll to the bottom if:
|
||||
// - the number of messages displayed changed
|
||||
|
@ -14,6 +14,8 @@ const {
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
|
||||
const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
|
||||
const TabboxPanel = createFactory(require("devtools/client/netmonitor/src/components/tabbox-panel"));
|
||||
const { PANELS } = require("devtools/client/netmonitor/src/constants");
|
||||
|
||||
NetworkEventMessage.displayName = "NetworkEventMessage";
|
||||
|
||||
@ -26,13 +28,29 @@ NetworkEventMessage.propTypes = {
|
||||
networkMessageUpdate: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* This component is responsible for rendering network messages
|
||||
* in the Console panel.
|
||||
*
|
||||
* Network logs are expandable and the user can inspect it inline
|
||||
* within the Console panel (no need to switch to the Network panel).
|
||||
*
|
||||
* HTTP details are rendered using `TabboxPanel` component used to
|
||||
* render contents of the side bar in the Network panel.
|
||||
*
|
||||
* All HTTP details data are fetched from the backend on-demand
|
||||
* when the user is expanding network log for the first time.
|
||||
*/
|
||||
function NetworkEventMessage({
|
||||
message = {},
|
||||
serviceContainer,
|
||||
timestampsVisible,
|
||||
networkMessageUpdate = {},
|
||||
dispatch,
|
||||
open,
|
||||
}) {
|
||||
const {
|
||||
id,
|
||||
actor,
|
||||
indent,
|
||||
source,
|
||||
@ -77,11 +95,28 @@ function NetworkEventMessage({
|
||||
|
||||
const messageBody = [method, xhr, url, statusBody];
|
||||
|
||||
// Only render the attachment if the network-event is
|
||||
// actually opened (performance optimization).
|
||||
const attachment = open && dom.div({className: "network-info devtools-monospace"},
|
||||
TabboxPanel({
|
||||
activeTabId: PANELS.HEADERS,
|
||||
request: networkMessageUpdate,
|
||||
sourceMapService: serviceContainer.sourceMapService,
|
||||
cloneSelectedRequest: () => {},
|
||||
selectTab: (tabId) => {},
|
||||
})
|
||||
);
|
||||
|
||||
return Message({
|
||||
dispatch,
|
||||
messageId: id,
|
||||
source,
|
||||
type,
|
||||
level,
|
||||
indent,
|
||||
collapsible: true,
|
||||
open,
|
||||
attachment,
|
||||
topLevelClasses,
|
||||
timeStamp,
|
||||
messageBody,
|
||||
|
@ -12,6 +12,7 @@ const actionTypes = {
|
||||
MESSAGE_OPEN: "MESSAGE_OPEN",
|
||||
MESSAGE_CLOSE: "MESSAGE_CLOSE",
|
||||
NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
|
||||
NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
|
||||
MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
|
||||
MESSAGE_OBJECT_PROPERTIES_RECEIVE: "MESSAGE_OBJECT_PROPERTIES_RECEIVE",
|
||||
MESSAGE_OBJECT_ENTRIES_RECEIVE: "MESSAGE_OBJECT_ENTRIES_RECEIVE",
|
||||
|
@ -52,6 +52,11 @@ NewConsoleOutputWrapper.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not focus if an input field was clicked
|
||||
if (target.closest("input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not focus if something other than the output region was clicked
|
||||
if (!target.closest(".webconsole-output")) {
|
||||
return;
|
||||
@ -219,6 +224,10 @@ NewConsoleOutputWrapper.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
dispatchRequestUpdate: function (id, data) {
|
||||
batchedMessageAdd(actions.networkUpdateRequest(id, data));
|
||||
},
|
||||
|
||||
// Should be used for test purpose only.
|
||||
getStore: function () {
|
||||
return store;
|
||||
|
@ -22,6 +22,10 @@ const {
|
||||
const { getGripPreviewItems } = require("devtools/client/shared/components/reps/reps");
|
||||
const { getSourceNames } = require("devtools/client/shared/source-utils");
|
||||
|
||||
const {
|
||||
UPDATE_PROPS
|
||||
} = require("devtools/client/netmonitor/src/constants");
|
||||
|
||||
const MessageState = Immutable.Record({
|
||||
// List of all the messages added to the console.
|
||||
messagesById: Immutable.OrderedMap(),
|
||||
@ -166,8 +170,10 @@ function messages(state = new MessageState(), action, filtersState, prefsState)
|
||||
return state.withMutations(function (record) {
|
||||
record.set("messagesUiById", messagesUiById.push(action.id));
|
||||
|
||||
let currMessage = messagesById.get(action.id);
|
||||
|
||||
// If the message is a group
|
||||
if (isGroupType(messagesById.get(action.id).type)) {
|
||||
if (isGroupType(currMessage.type)) {
|
||||
// We want to make its children visible
|
||||
const messagesToShow = [...messagesById].reduce((res, [id, message]) => {
|
||||
if (
|
||||
@ -195,6 +201,21 @@ function messages(state = new MessageState(), action, filtersState, prefsState)
|
||||
...visibleMessages.slice(insertIndex),
|
||||
]);
|
||||
}
|
||||
|
||||
// If the current message is a network event, mark it as opened-once,
|
||||
// so HTTP details are not fetched again the next time the user
|
||||
// opens the log.
|
||||
if (currMessage.source == "network") {
|
||||
record.set("messagesById",
|
||||
messagesById.set(
|
||||
action.id, Object.assign({},
|
||||
currMessage, {
|
||||
openedOnce: true
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
case constants.MESSAGE_CLOSE:
|
||||
@ -250,6 +271,44 @@ function messages(state = new MessageState(), action, filtersState, prefsState)
|
||||
})
|
||||
);
|
||||
|
||||
case constants.NETWORK_UPDATE_REQUEST: {
|
||||
let request = networkMessagesUpdateById[action.id];
|
||||
if (!request) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let values = {};
|
||||
for (let [key, value] of Object.entries(action.data)) {
|
||||
if (UPDATE_PROPS.includes(key)) {
|
||||
values[key] = value;
|
||||
|
||||
switch (key) {
|
||||
case "securityInfo":
|
||||
values.securityState = value.state;
|
||||
break;
|
||||
case "totalTime":
|
||||
values.totalTime = request.totalTime;
|
||||
break;
|
||||
case "requestPostData":
|
||||
values.requestHeadersFromUploadStream = {
|
||||
headers: [],
|
||||
headersSize: 0,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let newState = state.set(
|
||||
"networkMessagesUpdateById",
|
||||
Object.assign({}, networkMessagesUpdateById, {
|
||||
[action.id]: Object.assign({}, request, values)
|
||||
})
|
||||
);
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
case constants.REMOVED_ACTORS_CLEAR:
|
||||
return state.set("removedActors", []);
|
||||
|
||||
|
@ -17,13 +17,24 @@ const {
|
||||
} = require("devtools/client/shared/redux/middleware/debounce");
|
||||
const {
|
||||
MESSAGE_ADD,
|
||||
MESSAGE_OPEN,
|
||||
MESSAGES_CLEAR,
|
||||
REMOVED_ACTORS_CLEAR,
|
||||
NETWORK_MESSAGE_UPDATE,
|
||||
PREFS,
|
||||
} = require("devtools/client/webconsole/new-console-output/constants");
|
||||
const { reducers } = require("./reducers/index");
|
||||
const Services = require("Services");
|
||||
const {
|
||||
getMessage,
|
||||
getAllMessagesUiById,
|
||||
} = require("devtools/client/webconsole/new-console-output/selectors/messages");
|
||||
const DataProvider = require("devtools/client/netmonitor/src/connector/firefox-data-provider");
|
||||
|
||||
/**
|
||||
* Create and configure store for the Console panel. This is the place
|
||||
* where various enhancers and middleware can be registered.
|
||||
*/
|
||||
function configureStore(hud, options = {}) {
|
||||
const logLimit = options.logLimit
|
||||
|| Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1);
|
||||
@ -48,7 +59,12 @@ function configureStore(hud, options = {}) {
|
||||
return createStore(
|
||||
createRootReducer(),
|
||||
initialState,
|
||||
compose(applyMiddleware(thunk), enableActorReleaser(hud), enableBatching())
|
||||
compose(
|
||||
applyMiddleware(thunk),
|
||||
enableActorReleaser(hud),
|
||||
enableBatching(),
|
||||
enableNetProvider(hud)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -125,6 +141,69 @@ function enableActorReleaser(hud) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This enhancer is responsible for fetching HTTP details data
|
||||
* collected by the backend. The fetch happens on-demand
|
||||
* when the user expands network log in order to inspect it.
|
||||
*
|
||||
* This way we don't slow down the Console logging by fetching.
|
||||
* unnecessary data over RDP.
|
||||
*/
|
||||
function enableNetProvider(hud) {
|
||||
let dataProvider;
|
||||
return next => (reducer, initialState, enhancer) => {
|
||||
function netProviderEnhancer(state, action) {
|
||||
let proxy = hud ? hud.proxy : null;
|
||||
if (!proxy) {
|
||||
return reducer(state, action);
|
||||
}
|
||||
|
||||
let actions = {
|
||||
updateRequest: (id, data, batch) => {
|
||||
proxy.dispatchRequestUpdate(id, data);
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
});
|
||||
}
|
||||
|
||||
let type = action.type;
|
||||
|
||||
// If network message has been opened, fetch all
|
||||
// HTTP details from the backend.
|
||||
if (type == MESSAGE_OPEN) {
|
||||
let message = getMessage(state, action.id);
|
||||
if (!message.openedOnce && message.source == "network") {
|
||||
message.updates.forEach(updateType => {
|
||||
dataProvider.onNetworkEventUpdate(null, {
|
||||
packet: { updateType: updateType },
|
||||
networkInfo: message,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Process all incoming HTTP details packets.
|
||||
if (type == NETWORK_MESSAGE_UPDATE) {
|
||||
let open = getAllMessagesUiById(state).includes(action.id);
|
||||
if (open) {
|
||||
dataProvider.onNetworkEventUpdate(null, action.response);
|
||||
}
|
||||
}
|
||||
|
||||
return reducer(state, action);
|
||||
}
|
||||
|
||||
return next(netProviderEnhancer, initialState, enhancer);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Helper function for releasing backend actors.
|
||||
*/
|
||||
|
@ -60,5 +60,10 @@ exports.NetworkEventMessage = function (props) {
|
||||
timeStamp: null,
|
||||
totalTime: null,
|
||||
indent: 0,
|
||||
updates: null,
|
||||
openedOnce: false,
|
||||
securityState: null,
|
||||
securityInfo: null,
|
||||
requestHeadersFromUploadStream: null,
|
||||
}, props);
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
"use strict";
|
||||
|
||||
const l10n = require("devtools/client/webconsole/webconsole-l10n");
|
||||
const { getUrlDetails } = require("devtools/client/netmonitor/src/utils/request-utils");
|
||||
|
||||
const {
|
||||
MESSAGE_SOURCE,
|
||||
@ -232,6 +233,11 @@ function transformNetworkEventPacket(packet) {
|
||||
response: networkEvent.response,
|
||||
timeStamp: networkEvent.timeStamp,
|
||||
totalTime: networkEvent.totalTime,
|
||||
url: networkEvent.request.url,
|
||||
urlDetails: getUrlDetails(networkEvent.request.url),
|
||||
method: networkEvent.request.method,
|
||||
updates: networkEvent.updates,
|
||||
cause: networkEvent.cause,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ WebConsolePanel.prototype = {
|
||||
let msg = "WebConsolePanel open failed. " +
|
||||
reason.error + ": " + reason.message;
|
||||
dump(msg + "\n");
|
||||
console.error(msg);
|
||||
console.error(msg, reason);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -243,6 +243,10 @@ WebConsoleConnectionProxy.prototype = {
|
||||
this.webConsoleFrame.newConsoleOutput.dispatchMessageUpdate(networkInfo, response);
|
||||
},
|
||||
|
||||
dispatchRequestUpdate: function (id, data) {
|
||||
this.webConsoleFrame.newConsoleOutput.dispatchRequestUpdate(id, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* The "cachedMessages" response handler.
|
||||
*
|
||||
|
@ -11,6 +11,10 @@
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/webconsole.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/components-frame.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/reps/reps.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabs.css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabbar.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/netmonitor/src/assets/styles/netmonitor.css"/>
|
||||
|
||||
<script src="chrome://devtools/content/shared/theme-switching.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="resource://devtools/client/webconsole/new-console-output/main.js"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user