# HG changeset patch # User Jarda Snajdr Bug 1134073 - Part 1: Collect information about request cause and stacktrace in netmonitor backend r=ochameau diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index 449fc91..76bd804 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -13,16 +13,17 @@ const { EnvironmentActor } = require("devtools/server/actors/environment"); const { ThreadActor } = require("devtools/server/actors/script"); const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const ErrorDocs = require("devtools/server/actors/errordocs"); loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true); loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true); loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true); +loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsole/network-monitor", true); loader.lazyRequireGetter(this, "events", "sdk/event/core"); loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true); loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true); loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true); for (let name of ["WebConsoleUtils", "ConsoleServiceListener", "ConsoleAPIListener", "addWebConsoleCommands", "ConsoleReflowListener", "CONSOLE_WORKER_IDS"]) { @@ -593,30 +594,34 @@ WebConsoleActor.prototype = this.consoleAPIListener = new ConsoleAPIListener(window, this); this.consoleAPIListener.init(); } startedListeners.push(listener); break; case "NetworkActivity": if (!this.networkMonitor) { + // Create a StackTraceCollector that's going to be shared both by the + // NetworkMonitorChild (getting messages about requests from parent) and + // by the NetworkMonitor that directly watches service workers requests. + this.stackTraceCollector = new StackTraceCollector({ window, appId }); + this.stackTraceCollector.init(); + if (appId || messageManager) { // Start a network monitor in the parent process to listen to // most requests than happen in parent this.networkMonitor = new NetworkMonitorChild(appId, messageManager, this.parentActor.actorID, this); this.networkMonitor.init(); // Spawn also one in the child to listen to service workers - this.networkMonitorChild = new NetworkMonitor({ window: window }, - this); + this.networkMonitorChild = new NetworkMonitor({ window }, this); this.networkMonitorChild.init(); - } - else { - this.networkMonitor = new NetworkMonitor({ window: window }, this); + } else { + this.networkMonitor = new NetworkMonitor({ window }, this); this.networkMonitor.init(); } } startedListeners.push(listener); break; case "FileActivity": if (this.window instanceof Ci.nsIDOMWindow) { if (!this.consoleProgressListener) { @@ -695,16 +700,20 @@ WebConsoleActor.prototype = if (this.networkMonitor) { this.networkMonitor.destroy(); this.networkMonitor = null; } if (this.networkMonitorChild) { this.networkMonitorChild.destroy(); this.networkMonitorChild = null; } + if (this.stackTraceCollector) { + this.stackTraceCollector.destroy(); + this.stackTraceCollector = null; + } stoppedListeners.push(listener); break; case "FileActivity": if (this.consoleProgressListener) { this.consoleProgressListener.stopMonitor(this.consoleProgressListener. MONITOR_FILE_ACTIVITY); this.consoleProgressListener = null; } @@ -1823,16 +1832,17 @@ NetworkEventActor.prototype = { return { actor: this.actorID, startedDateTime: this._startedDateTime, timeStamp: Date.parse(this._startedDateTime), url: this._request.url, method: this._request.method, isXHR: this._isXHR, + cause: this._cause, fromCache: this._fromCache, fromServiceWorker: this._fromServiceWorker, private: this._private, }; }, /** * Releases this actor from the pool. @@ -1868,16 +1878,17 @@ NetworkEventActor.prototype = * * @param object aNetworkEvent * The network event associated with this actor. */ init: function NEA_init(aNetworkEvent) { this._startedDateTime = aNetworkEvent.startedDateTime; this._isXHR = aNetworkEvent.isXHR; + this._cause = aNetworkEvent.cause; this._fromCache = aNetworkEvent.fromCache; this._fromServiceWorker = aNetworkEvent.fromServiceWorker; for (let prop of ["method", "url", "httpVersion", "headersSize"]) { this._request[prop] = aNetworkEvent[prop]; } this._discardRequestBody = aNetworkEvent.discardRequestBody; diff --git a/devtools/shared/webconsole/client.js b/devtools/shared/webconsole/client.js index 39d9c7f..841d608 100644 --- a/devtools/shared/webconsole/client.js +++ b/devtools/shared/webconsole/client.js @@ -95,16 +95,17 @@ WebConsoleClient.prototype = { discardRequestBody: true, discardResponseBody: true, startedDateTime: actor.startedDateTime, request: { url: actor.url, method: actor.method, }, isXHR: actor.isXHR, + cause: actor.cause, response: {}, timings: {}, // track the list of network event updates updates: [], private: actor.private, fromCache: actor.fromCache, fromServiceWorker: actor.fromServiceWorker }; diff --git a/devtools/shared/webconsole/network-monitor.js b/devtools/shared/webconsole/network-monitor.js index 64695b2..bfc7c5c 100644 --- a/devtools/shared/webconsole/network-monitor.js +++ b/devtools/shared/webconsole/network-monitor.js @@ -1,17 +1,17 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft= javascript ts=2 et sw=2 tw=80: */ /* 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 {Cc, Ci, Cu, Cr} = require("chrome"); +const {Cc, Ci, Cm, Cu, Cr, components} = require("chrome"); const Services = require("Services"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); loader.lazyRequireGetter(this, "NetworkHelper", "devtools/shared/webconsole/network-helper"); loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/shared/DevToolsUtils"); @@ -32,16 +32,244 @@ const HTTP_MOVED_PERMANENTLY = 301; const HTTP_FOUND = 302; const HTTP_SEE_OTHER = 303; const HTTP_TEMPORARY_REDIRECT = 307; // The maximum number of bytes a NetworkResponseListener can hold: 1 MB const RESPONSE_BODY_LIMIT = 1048576; /** + * Check if a given network request should be logged by a network monitor + * based on the specified filters. + * + * @param nsIHttpChannel channel + * Request to check. + * @param filters + * NetworkMonitor filters to match against. + * @return boolean + * True if the network request should be logged, false otherwise. + */ +function matchRequest(channel, filters) { + // Log everything if no filter is specified + if (!filters.topFrame && !filters.window && !filters.appId) { + return true; + } + + // Ignore requests from chrome or add-on code when we are monitoring + // content. + // TODO: one particular test (browser_styleeditor_fetch-from-cache.js) needs + // the DevToolsUtils.testing check. We will move to a better way to serve + // its needs in bug 1167188, where this check should be removed. + if (!DevToolsUtils.testing && channel.loadInfo && + channel.loadInfo.loadingDocument === null && + channel.loadInfo.loadingPrincipal === + Services.scriptSecurityManager.getSystemPrincipal()) { + return false; + } + + if (filters.window) { + // Since frames support, this.window may not be the top level content + // frame, so that we can't only compare with win.top. + let win = NetworkHelper.getWindowForRequest(channel); + while (win) { + if (win == filters.window) { + return true; + } + if (win.parent == win) { + break; + } + win = win.parent; + } + } + + if (filters.topFrame) { + let topFrame = NetworkHelper.getTopFrameForRequest(channel); + if (topFrame && topFrame === filters.topFrame) { + return true; + } + } + + if (filters.appId) { + let appId = NetworkHelper.getAppIdForRequest(channel); + if (appId && appId == filters.appId) { + return true; + } + } + + // The following check is necessary because beacon channels don't come + // associated with a load group. Bug 1160837 will hopefully introduce a + // platform fix that will render the following code entirely useless. + if (channel.loadInfo && + channel.loadInfo.externalContentPolicyType == + Ci.nsIContentPolicy.TYPE_BEACON) { + let nonE10sMatch = filters.window && + channel.loadInfo.loadingDocument === filters.window.document; + const loadingPrincipal = channel.loadInfo.loadingPrincipal; + let e10sMatch = filters.topFrame && + filters.topFrame.contentPrincipal && + filters.topFrame.contentPrincipal.equals(loadingPrincipal) && + filters.topFrame.contentPrincipal.URI.spec == channel.referrer.spec; + let b2gMatch = filters.appId && loadingPrincipal.appId === filters.appId; + if (nonE10sMatch || e10sMatch || b2gMatch) { + return true; + } + } + + return false; +} + +/** + * This is a nsIChannelEventSink implementation that monitors channel redirects and + * informs the registered StackTraceCollector about the old and new channels. + */ +const ChannelEventSink = { + classDescription: "NetworkMonitor Channel Event Sink", + classID: components.ID("{e89fa076-c845-48a8-8c45-2604729eba1d}"), + contractID: "@mozilla.org/network/monitor/channeleventsink;1", + categoryName: "net-channel-event-sinks", + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink, Ci.nsIFactory]), + + createInstance(outer, iid) { + if (outer) { + throw Cr.NS_ERROR_NO_AGGREGATION; + } + return this.QueryInterface(iid); + }, + + get wrappedJSObject() { + return this; + }, + + init() { + let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + if (registrar.isCIDRegistered(this.classID)) { + return; + } + + registrar.registerFactory(this.classID, this.classDescription, this.contractID, this); + XPCOMUtils.categoryManager.addCategoryEntry(this.categoryName, + this.contractID, this.contractID, false, true); + }, + + cleanup() { + XPCOMUtils.categoryManager.deleteCategoryEntry(this.categoryName, + this.contractID, false); + }, + + register(collector) { + this.init(); + + // Add a collector to the registered service instance. If we load this file several + // times into different globals, "this" is no longer the right singleton. + let me = Cc[this.contractID].getService(Ci.nsIChannelEventSink).wrappedJSObject; + me.collectors.add(collector); + }, + + unregister(collector) { + let me = Cc[this.contractID].getService(Ci.nsIChannelEventSink).wrappedJSObject; + me.collectors.delete(collector); + + if (me.collectors.size == 0) { + me.cleanup(); + } + }, + + asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) { + for (let collector of this.collectors) { + try { + collector.onChannelRedirect(oldChannel, newChannel, flags); + } catch (ex) { + console.error("StackTraceCollector.onChannelRedirect threw an exception", ex); + } + } + callback.onRedirectVerifyCallback(Cr.NS_OK); + }, + + collectors: new Set() +}; + +function StackTraceCollector(filters) { + this.filters = filters; + this.stacktracesById = new Map(); +} + +StackTraceCollector.prototype = { + init() { + Services.obs.addObserver(this, "http-on-opening-request", false); + ChannelEventSink.register(this); + }, + + destroy() { + Services.obs.removeObserver(this, "http-on-opening-request"); + ChannelEventSink.unregister(this); + }, + + _saveStackTrace(channel, stacktrace) { + this.stacktracesById.set(channel.channelId, stacktrace); + }, + + observe(subject) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + + if (!matchRequest(channel, this.filters)) { + return; + } + + // Convert the nsIStackFrame XPCOM objects to a nice JSON that can be + // passed around through message managers etc. + let frame = components.stack; + let stacktrace = []; + if (frame && frame.caller) { + frame = frame.caller; + while (frame) { + stacktrace.push({ + filename: frame.filename, + lineNumber: frame.lineNumber, + columnNumber: frame.columnNumber, + functionName: frame.name + }); + if (frame.asyncCaller) { + frame = frame.asyncCaller; + } else { + frame = frame.caller; + } + } + } + + this._saveStackTrace(channel, stacktrace); + }, + + onChannelRedirect(oldChannel, newChannel, flags) { + // We can be called with any nsIChannel, but are interested only in HTTP channels + try { + oldChannel.QueryInterface(Ci.nsIHttpChannel); + newChannel.QueryInterface(Ci.nsIHttpChannel); + } catch (ex) { + return; + } + + let oldId = oldChannel.channelId; + let stacktrace = this.stacktracesById.get(oldId); + if (stacktrace) { + this.stacktracesById.delete(oldId); + this._saveStackTrace(newChannel, stacktrace); + } + }, + + getStackTrace(channelId) { + let trace = this.stacktracesById.get(channelId); + this.stacktracesById.delete(channelId); + return trace; + } +}; + +exports.StackTraceCollector = StackTraceCollector; + +/** * The network response listener implements the nsIStreamListener and * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature * to get the response body of the request. * * The code is mostly based on code listings from: * * http://www.softwareishard.com/blog/firebug/ * nsitraceablechannel-intercept-http-traffic/ @@ -58,17 +286,16 @@ function NetworkResponseListener(owner, httpActivity) { this.owner = owner; this.receivedData = ""; this.httpActivity = httpActivity; this.bodySize = 0; let channel = this.httpActivity.channel; this._wrappedNotificationCallbacks = channel.notificationCallbacks; channel.notificationCallbacks = this; } -exports.NetworkResponseListener = NetworkResponseListener; NetworkResponseListener.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback, Ci.nsIRequestObserver, Ci.nsIInterfaceRequestor, Ci.nsISupports]), // nsIInterfaceRequestor implementation @@ -457,56 +684,48 @@ NetworkResponseListener.prototype = { * requests. The nsIObserverService is also used for monitoring * http-on-examine-response notifications. All network request information is * routed to the remote Web Console. * * @constructor * @param object filters * Object with the filters to use for network requests: * - window (nsIDOMWindow): filter network requests by the associated - * window object. + * window object. * - appId (number): filter requests by the appId. * - topFrame (nsIDOMElement): filter requests by their topFrameElement. * Filters are optional. If any of these filters match the request is * logged (OR is applied). If no filter is provided then all requests are * logged. * @param object owner * The network monitor owner. This object needs to hold: * - onNetworkEvent(requestInfo, channel, networkMonitor). - * This method is invoked once for every new network request and it is - * given the following arguments: the initial network request - * information, and the channel. The third argument is the NetworkMonitor - * instance. - * onNetworkEvent() must return an object which holds several add*() - * methods which are used to add further network request/response - * information. + * This method is invoked once for every new network request and it is + * given the following arguments: the initial network request + * information, and the channel. The third argument is the NetworkMonitor + * instance. onNetworkEvent() must return an object which holds several add*() + * methods which are used to add further network request/response + * information. + * - stackTraceCollector If the owner has this optional property, it will + * be used as a StackTraceCollector by the NetworkMonitor. */ function NetworkMonitor(filters, owner) { - if (filters) { - this.window = filters.window; - this.appId = filters.appId; - this.topFrame = filters.topFrame; - } - if (!this.window && !this.appId && !this.topFrame) { - this._logEverything = true; - } + this.filters = filters; this.owner = owner; this.openRequests = {}; this.openResponses = {}; this._httpResponseExaminer = DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this); this._serviceWorkerRequest = this._serviceWorkerRequest.bind(this); } + exports.NetworkMonitor = NetworkMonitor; NetworkMonitor.prototype = { - _logEverything: false, - window: null, - appId: null, - topFrame: null, + filters: null, httpTransactionCodes: { 0x5001: "REQUEST_HEADER", 0x5002: "REQUEST_BODY_SENT", 0x5003: "RESPONSE_START", 0x5004: "RESPONSE_HEADER", 0x5005: "RESPONSE_COMPLETE", 0x5006: "TRANSACTION_CLOSE", @@ -561,17 +780,17 @@ NetworkMonitor.prototype = { // everything else only happens in the parent process Services.obs.addObserver(this._serviceWorkerRequest, "service-worker-synthesized-response", false); }, _serviceWorkerRequest: function (subject, topic, data) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); - if (!this._matchRequest(channel)) { + if (!matchRequest(channel, this.filters)) { return; } this.interceptedChannels.add(subject); // On e10s, we never receive http-on-examine-cached-response, so fake one. if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) { this._httpResponseExaminer(channel, "http-on-examine-cached-response"); @@ -597,17 +816,17 @@ NetworkMonitor.prototype = { (topic != "http-on-examine-response" && topic != "http-on-examine-cached-response") || !(subject instanceof Ci.nsIHttpChannel)) { return; } let channel = subject.QueryInterface(Ci.nsIHttpChannel); - if (!this._matchRequest(channel)) { + if (!matchRequest(channel, this.filters)) { return; } let response = { id: gSequenceId(), channel: channel, headers: [], cookies: [], @@ -752,94 +971,16 @@ NetworkMonitor.prototype = { this._onTransactionClose(httpActivity); break; default: break; } }), /** - * Check if a given network request should be logged by this network monitor - * instance based on the current filters. - * - * @private - * @param nsIHttpChannel channel - * Request to check. - * @return boolean - * True if the network request should be logged, false otherwise. - */ - _matchRequest: function (channel) { - if (this._logEverything) { - return true; - } - - // Ignore requests from chrome or add-on code when we are monitoring - // content. - // TODO: one particular test (browser_styleeditor_fetch-from-cache.js) needs - // the DevToolsUtils.testing check. We will move to a better way to serve - // its needs in bug 1167188, where this check should be removed. - if (!DevToolsUtils.testing && channel.loadInfo && - channel.loadInfo.loadingDocument === null && - channel.loadInfo.loadingPrincipal === - Services.scriptSecurityManager.getSystemPrincipal()) { - return false; - } - - if (this.window) { - // Since frames support, this.window may not be the top level content - // frame, so that we can't only compare with win.top. - let win = NetworkHelper.getWindowForRequest(channel); - while (win) { - if (win == this.window) { - return true; - } - if (win.parent == win) { - break; - } - win = win.parent; - } - } - - if (this.topFrame) { - let topFrame = NetworkHelper.getTopFrameForRequest(channel); - if (topFrame && topFrame === this.topFrame) { - return true; - } - } - - if (this.appId) { - let appId = NetworkHelper.getAppIdForRequest(channel); - if (appId && appId == this.appId) { - return true; - } - } - - // The following check is necessary because beacon channels don't come - // associated with a load group. Bug 1160837 will hopefully introduce a - // platform fix that will render the following code entirely useless. - if (channel.loadInfo && - channel.loadInfo.externalContentPolicyType == - Ci.nsIContentPolicy.TYPE_BEACON) { - let nonE10sMatch = this.window && - channel.loadInfo.loadingDocument === this.window.document; - const loadingPrincipal = channel.loadInfo.loadingPrincipal; - let e10sMatch = this.topFrame && - this.topFrame.contentPrincipal && - this.topFrame.contentPrincipal.equals(loadingPrincipal) && - this.topFrame.contentPrincipal.URI.spec == channel.referrer.spec; - let b2gMatch = this.appId && loadingPrincipal.appId === this.appId; - if (nonE10sMatch || e10sMatch || b2gMatch) { - return true; - } - } - - return false; - }, - - /** * */ _createNetworkEvent: function (channel, { timestamp, extraStringData, fromCache, fromServiceWorker }) { let win = NetworkHelper.getWindowForRequest(channel); let httpActivity = this.createActivityObject(channel); // see _onRequestBodySent() @@ -852,36 +993,51 @@ NetworkMonitor.prototype = { httpActivity.timings.REQUEST_HEADER = { first: timestamp, last: timestamp }; } let event = {}; event.method = channel.requestMethod; + event.channelId = channel.channelId; event.url = channel.URI.spec; event.private = httpActivity.private; event.headersSize = 0; event.startedDateTime = (timestamp ? new Date(Math.round(timestamp / 1000)) : new Date()) .toISOString(); event.fromCache = fromCache; event.fromServiceWorker = fromServiceWorker; httpActivity.fromServiceWorker = fromServiceWorker; if (extraStringData) { event.headersSize = extraStringData.length; } - // Determine if this is an XHR request. + // Determine the cause and if this is an XHR request. + let causeType = channel.loadInfo.externalContentPolicyType; + let loadingPrincipal = channel.loadInfo.loadingPrincipal; + let causeUri = loadingPrincipal ? loadingPrincipal.URI : null; + let stacktrace; + // If this is the parent process, there is no stackTraceCollector - the stack + // trace will be added in NetworkMonitorChild._onNewEvent. + if (this.owner.stackTraceCollector) { + stacktrace = this.owner.stackTraceCollector.getStackTrace(event.channelId); + } + + event.cause = { + type: causeType, + loadingDocumentUri: causeUri ? causeUri.spec : null, + stacktrace + }; + httpActivity.isXHR = event.isXHR = - (channel.loadInfo.externalContentPolicyType === - Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST || - channel.loadInfo.externalContentPolicyType === - Ci.nsIContentPolicy.TYPE_FETCH); + (causeType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST || + causeType === Ci.nsIContentPolicy.TYPE_FETCH); // Determine the HTTP version. let httpVersionMaj = {}; let httpVersionMin = {}; channel.QueryInterface(Ci.nsIHttpChannelInternal); channel.getRequestVersion(httpVersionMaj, httpVersionMin); event.httpVersion = "HTTP/" + httpVersionMaj.value + "." + @@ -927,22 +1083,21 @@ NetworkMonitor.prototype = { * * @private * @param nsIHttpChannel channel * @param number timestamp * @param string extraStringData * @return void */ _onRequestHeader: function (channel, timestamp, extraStringData) { - if (!this._matchRequest(channel)) { + if (!matchRequest(channel, this.filters)) { return; } - this._createNetworkEvent(channel, { timestamp: timestamp, - extraStringData: extraStringData }); + this._createNetworkEvent(channel, { timestamp, extraStringData }); }, /** * Create the empty HTTP activity object. This object is used for storing all * the request and response information. * * This is a HAR-like object. Conformance to the spec is not guaranteed at * this point. @@ -1222,18 +1377,17 @@ NetworkMonitor.prototype = { Services.obs.removeObserver(this._serviceWorkerRequest, "service-worker-synthesized-response"); this.interceptedChannels.clear(); this.openRequests = {}; this.openResponses = {}; this.owner = null; - this.window = null; - this.topFrame = null; + this.filters = null; }, }; /** * The NetworkMonitorChild is used to proxy all of the network activity of the * child app process from the main process. The child WebConsoleActor creates an * instance of this object. * @@ -1259,33 +1413,33 @@ function NetworkMonitorChild(appId, messageManager, connID, owner) { this.appId = appId; this.connID = connID; this.owner = owner; this._messageManager = messageManager; this._onNewEvent = this._onNewEvent.bind(this); this._onUpdateEvent = this._onUpdateEvent.bind(this); this._netEvents = new Map(); } + exports.NetworkMonitorChild = NetworkMonitorChild; NetworkMonitorChild.prototype = { appId: null, owner: null, _netEvents: null, _saveRequestAndResponseBodies: true, get saveRequestAndResponseBodies() { return this._saveRequestAndResponseBodies; }, set saveRequestAndResponseBodies(val) { this._saveRequestAndResponseBodies = val; this._messageManager.sendAsyncMessage("debug:netmonitor:" + this.connID, { - appId: this.appId, action: "setPreferences", preferences: { saveRequestAndResponseBodies: this._saveRequestAndResponseBodies, }, }); }, init: function () { @@ -1297,16 +1451,23 @@ NetworkMonitorChild.prototype = { mm.sendAsyncMessage("debug:netmonitor:" + this.connID, { appId: this.appId, action: "start", }); }, _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) { let {id, event} = msg.data; + + // Try to add stack trace to the event data received from parent + if (this.owner.stackTraceCollector) { + event.cause.stacktrace = + this.owner.stackTraceCollector.getStackTrace(event.channelId); + } + let actor = this.owner.onNetworkEvent(event); this._netEvents.set(id, Cu.getWeakReference(actor)); }), _onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) { let {id, method, args} = msg.data; let weakActor = this._netEvents.get(id); let actor = weakActor ? weakActor.get() : null; @@ -1443,21 +1604,22 @@ NetworkMonitorManager.prototype = { /** * Handler for "debug:monitor" messages received through the message manager * from the content process. * * @param object msg * Message from the content. */ onNetMonitorMessage: DevToolsUtils.makeInfallible(function (msg) { - let { action, appId } = msg.json; + let {action} = msg.json; // Pipe network monitor data from parent to child via the message manager. switch (action) { case "start": if (!this.netMonitor) { + let {appId} = msg.json; this.netMonitor = new NetworkMonitor({ topFrame: this.frame, appId: appId, }, this); this.netMonitor.init(); } break;