Bug 1416201 - HTTP request stack trace should be lazy loaded;r=rickychien
MozReview-Commit-ID: 5SWLLcNqORz
--- a/devtools/client/netmonitor/src/components/StackTracePanel.js
+++ b/devtools/client/netmonitor/src/components/StackTracePanel.js
@@ -1,51 +1,91 @@
/* 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/. */
"use strict";
-const { createFactory } = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { div } = dom;
// Components
const StackTrace = createFactory(require("devtools/client/shared/components/StackTrace"));
/**
* This component represents a side panel responsible for
* rendering stack-trace info for selected request.
*/
-function StackTracePanel({
- connector,
- openLink,
- request,
- sourceMapService,
-}) {
- let { stacktrace } = request.cause;
+class StackTracePanel extends Component {
+ static get propTypes() {
+ return {
+ connector: PropTypes.object.isRequired,
+ request: PropTypes.object.isRequired,
+ sourceMapService: PropTypes.object,
+ openLink: PropTypes.func,
+ };
+ }
+
+ /**
+ * `componentDidMount` is called when opening the StackTracePanel
+ * for the first time
+ */
+ componentDidMount() {
+ this.maybeFetchStackTrace(this.props);
+ }
+
+ /**
+ * `componentWillReceiveProps` is the only method called when
+ * switching between two requests while this panel is displayed.
+ */
+ componentWillReceiveProps(nextProps) {
+ this.maybeFetchStackTrace(nextProps);
+ }
- return (
- div({ className: "panel-container" },
- StackTrace({
- stacktrace,
- onViewSourceInDebugger: ({ url, line }) => {
- return connector.viewSourceInDebugger(url, line);
- },
- sourceMapService,
- openLink,
- }),
- )
- );
+ /**
+ * When switching to another request, lazily fetch stack-trace
+ * from the backend. This Panel will first be empty and then
+ * display the content.
+ */
+ maybeFetchStackTrace(props) {
+ // Fetch stack trace only if it's available and not yet
+ // on the client.
+ if (!props.request.stacktrace &&
+ props.request.cause.stacktraceAvailable) {
+ // This method will set `props.request.stacktrace`
+ // asynchronously and force another render.
+ props.connector.requestData(props.request.id, "stackTrace");
+ }
+ }
+
+ // Rendering
+
+ render() {
+ let {
+ connector,
+ openLink,
+ request,
+ sourceMapService,
+ } = this.props;
+
+ let {
+ stacktrace = []
+ } = request;
+
+ return (
+ div({ className: "panel-container" },
+ StackTrace({
+ stacktrace,
+ onViewSourceInDebugger: ({ url, line }) => {
+ return connector.viewSourceInDebugger(url, line);
+ },
+ sourceMapService,
+ openLink,
+ }),
+ )
+ );
+ }
}
-StackTracePanel.displayName = "StackTracePanel";
-
-StackTracePanel.propTypes = {
- connector: PropTypes.object.isRequired,
- request: PropTypes.object.isRequired,
- sourceMapService: PropTypes.object,
- openLink: PropTypes.func,
-};
-
module.exports = StackTracePanel;
--- a/devtools/client/netmonitor/src/components/TabboxPanel.js
+++ b/devtools/client/netmonitor/src/components/TabboxPanel.js
@@ -79,17 +79,17 @@ function TabboxPanel({
ResponsePanel({ request, openLink, connector }),
),
TabPanel({
id: PANELS.TIMINGS,
title: TIMINGS_TITLE,
},
TimingsPanel({ request }),
),
- request.cause && request.cause.stacktrace && request.cause.stacktrace.length > 0 &&
+ request.cause && request.cause.stacktraceAvailable &&
TabPanel({
id: PANELS.STACK_TRACE,
title: STACK_TRACE_TITLE,
},
StackTracePanel({ connector, openLink, request, sourceMapService }),
),
request.securityState && request.securityState !== "insecure" &&
TabPanel({
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -61,16 +61,22 @@ class FirefoxDataProvider {
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,
+
+ // Compatibility code to support Firefox 58 and earlier that always
+ // send stack-trace immediately on networkEvent message.
+ // FF59+ supports fetching the traces lazily via requestData.
+ stacktrace: cause.stacktrace,
+
fromCache,
fromServiceWorker},
true,
);
}
emit(EVENTS.REQUEST_ADDED, id);
}
@@ -656,16 +662,29 @@ class FirefoxDataProvider {
*/
onEventTimings(response) {
return this.updateRequest(response.from, {
eventTimings: response
}).then(() => {
emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
});
}
+
+ /**
+ * Handles information received for a "stackTrace" packet.
+ *
+ * @param {object} response the message received from the server.
+ */
+ onStackTrace(response) {
+ return this.updateRequest(response.from, {
+ stacktrace: response.stacktrace
+ }).then(() => {
+ emit(EVENTS.RECEIVED_EVENT_STACKTRACE, response.from);
+ });
+ }
}
/**
* Guard 'emit' to avoid exception in non-window environment.
*/
function emit(type, data) {
if (typeof window != "undefined") {
window.emit(type, data);
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -88,16 +88,19 @@ const EVENTS = {
UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
// When response content begins, updates and finishes receiving.
STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
+ // When stack-trace finishes receiving.
+ RECEIVED_EVENT_STACKTRACE: "NetMonitor:NetworkEventUpdated:StackTrace",
+
// Fired once the connection is established
CONNECTED: "connected",
// When request payload (HTTP details data) are fetched from the backend.
PAYLOAD_READY: "NetMonitor:PayloadReady",
};
const UPDATE_PROPS = [
@@ -121,16 +124,17 @@ const UPDATE_PROPS = [
"requestHeadersFromUploadStream",
"requestCookies",
"requestPostData",
"responseHeaders",
"responseCookies",
"responseContent",
"responseContentAvailable",
"formDataSections",
+ "stacktrace",
];
const PANELS = {
COOKIES: "cookies",
HEADERS: "headers",
PARAMS: "params",
RESPONSE: "response",
SECURITY: "security",
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1931,16 +1931,17 @@ function NetworkEventActor(webConsoleAct
this._response = {
headers: [],
cookies: [],
content: {},
};
this._timings = {};
+ this._stackTrace = {};
// Keep track of LongStringActors owned by this NetworkEventActor.
this._longStringActors = new Set();
}
NetworkEventActor.prototype =
{
_request: null,
@@ -2003,16 +2004,24 @@ NetworkEventActor.prototype =
*/
init: function (networkEvent) {
this._startedDateTime = networkEvent.startedDateTime;
this._isXHR = networkEvent.isXHR;
this._cause = networkEvent.cause;
this._fromCache = networkEvent.fromCache;
this._fromServiceWorker = networkEvent.fromServiceWorker;
+ // Stack trace info isn't sent automatically. The client
+ // needs to request it explicitly using getStackTrace
+ // packet.
+ this._stackTrace = networkEvent.cause.stacktrace;
+ delete networkEvent.cause.stacktrace;
+ networkEvent.cause.stacktraceAvailable =
+ !!(this._stackTrace && this._stackTrace.length);
+
for (let prop of ["method", "url", "httpVersion", "headersSize"]) {
this._request[prop] = networkEvent[prop];
}
this._discardRequestBody = networkEvent.discardRequestBody;
this._discardResponseBody = networkEvent.discardResponseBody;
this._private = networkEvent.private;
},
@@ -2124,16 +2133,29 @@ NetworkEventActor.prototype =
return {
from: this.actorID,
timings: this._timings,
totalTime: this._totalTime,
offsets: this._offsets
};
},
+ /**
+ * The "getStackTrace" packet type handler.
+ *
+ * @return object
+ * The response packet - stack trace.
+ */
+ onGetStackTrace: function () {
+ return {
+ from: this.actorID,
+ stacktrace: this._stackTrace,
+ };
+ },
+
/** ****************************************************************
* Listeners for new network event data coming from NetworkMonitor.
******************************************************************/
/**
* Add network request headers.
*
* @param array headers
@@ -2372,9 +2394,10 @@ NetworkEventActor.prototype.requestTypes
"getRequestHeaders": NetworkEventActor.prototype.onGetRequestHeaders,
"getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies,
"getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData,
"getResponseHeaders": NetworkEventActor.prototype.onGetResponseHeaders,
"getResponseCookies": NetworkEventActor.prototype.onGetResponseCookies,
"getResponseContent": NetworkEventActor.prototype.onGetResponseContent,
"getEventTimings": NetworkEventActor.prototype.onGetEventTimings,
"getSecurityInfo": NetworkEventActor.prototype.onGetSecurityInfo,
+ "getStackTrace": NetworkEventActor.prototype.onGetStackTrace,
};
--- a/devtools/shared/webconsole/client.js
+++ b/devtools/shared/webconsole/client.js
@@ -562,16 +562,34 @@ WebConsoleClient.prototype = {
let packet = {
to: actor,
type: "getSecurityInfo",
};
return this._client.request(packet, onResponse);
},
/**
+ * Retrieve the stack-trace information for the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the stack-trace is received.
+ * @return request
+ * Request object that implements both Promise and EventEmitter interfaces
+ */
+ getStackTrace: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getStackTrace",
+ };
+ return this._client.request(packet, onResponse);
+ },
+
+ /**
* Send a HTTP request with the given data.
*
* @param string data
* The details of the HTTP request.
* @param function onResponse
* The function invoked when the response is received.
* @return request
* Request object that implements both Promise and EventEmitter interfaces