mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
Bug 1663523 - [devtools] Add the network event stacktrace watcher r=ochameau,devtools-backward-compat-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D86809
This commit is contained in:
parent
61728de827
commit
3d1f26e2cd
@ -41,6 +41,7 @@ DevToolsModules(
|
||||
'root.js',
|
||||
'screenshot.js',
|
||||
'source.js',
|
||||
'stacktraces.js',
|
||||
'storage.js',
|
||||
'string.js',
|
||||
'styles.js',
|
||||
|
22
devtools/client/fronts/stacktraces.js
Normal file
22
devtools/client/fronts/stacktraces.js
Normal file
@ -0,0 +1,22 @@
|
||||
/* 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 {
|
||||
FrontClassWithSpec,
|
||||
registerFront,
|
||||
} = require("devtools/shared/protocol");
|
||||
const { stackTracesSpec } = require("devtools/shared/specs/stacktraces");
|
||||
|
||||
class StackTracesFront extends FrontClassWithSpec(stackTracesSpec) {
|
||||
constructor(client, targetFront, parentFront) {
|
||||
super(client, targetFront, parentFront);
|
||||
// Attribute name from which to retrieve the actorID out of the target actor's form
|
||||
this.formAttributeName = "stacktracesActor";
|
||||
}
|
||||
}
|
||||
|
||||
exports.StackTracesFront = StackTracesFront;
|
||||
registerFront(StackTracesFront);
|
@ -140,6 +140,7 @@ class FirefoxConnector {
|
||||
webConsoleFront: this.webConsoleFront,
|
||||
actions: this.actions,
|
||||
owner: this.owner,
|
||||
resourceWatcher: this.toolbox.resourceWatcher,
|
||||
});
|
||||
|
||||
// Register target listeners if we switched to a new top level one
|
||||
|
@ -27,17 +27,21 @@ class FirefoxDataProvider {
|
||||
* Constructor for data provider
|
||||
*
|
||||
* @param {Object} webConsoleFront represents the client object for Console actor.
|
||||
* @param {Object} actions set of actions fired during data fetching process
|
||||
* @params {Object} owner all events are fired on this object
|
||||
* @param {Object} actions set of actions fired during data fetching process.
|
||||
* @param {Object} owner all events are fired on this object.
|
||||
* @param {Object} resourceWatcher enables checking for watcher support
|
||||
*/
|
||||
constructor({ webConsoleFront, actions, owner }) {
|
||||
constructor({ webConsoleFront, actions, owner, resourceWatcher }) {
|
||||
// Options
|
||||
this.webConsoleFront = webConsoleFront;
|
||||
this.actions = actions || {};
|
||||
this.actionsEnabled = true;
|
||||
this.owner = owner;
|
||||
// Map of all stacktrace resources keyed by network event's channelId
|
||||
this.resourceWatcher = resourceWatcher;
|
||||
// Map of all stacktrace resources keyed by network event's resourceId
|
||||
this.stackTraces = new Map();
|
||||
// Map of the stacktrace information keyed by the actor id's
|
||||
this.stackTraceRequestInfoByActorID = new Map();
|
||||
|
||||
// Internal properties
|
||||
this.payloadQueue = new Map();
|
||||
@ -322,8 +326,31 @@ class FirefoxDataProvider {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the stack-trace information for the given StackTracesActor.
|
||||
*
|
||||
* @param object actor
|
||||
* - {Object} targetFront: the target front.
|
||||
*
|
||||
* - {String} resourceId: the resource id for the network request".
|
||||
* @return {object}
|
||||
*/
|
||||
async _getStackTraceFromWatcher(actor) {
|
||||
const stacktracesFront = await actor.targetFront.getFront("stacktraces");
|
||||
const stacktrace = await stacktracesFront.getStackTrace(
|
||||
actor.stacktraceResourceId
|
||||
);
|
||||
return { stacktrace };
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler for when the network event stacktrace resource is available.
|
||||
* The resource contains basic info, the actual stacktrace is fetched lazily
|
||||
* using requestData.
|
||||
* @param {object} resource The network event stacktrace resource
|
||||
*/
|
||||
onStackTraceAvailable(resource) {
|
||||
this.stackTraces.set(resource.channelId, resource);
|
||||
this.stackTraces.set(resource.resourceId, resource);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -345,15 +372,31 @@ class FirefoxDataProvider {
|
||||
referrerPolicy,
|
||||
blockedReason,
|
||||
blockingExtension,
|
||||
channelId,
|
||||
resourceId,
|
||||
stacktraceResourceId,
|
||||
} = resource;
|
||||
|
||||
// Check if a stacktrace resource exists for this network event
|
||||
if (this.stackTraces.has(channelId)) {
|
||||
const { stacktrace, lastFrame } = this.stackTraces.get(channelId);
|
||||
cause.stacktraceAvailable = stacktrace;
|
||||
// Check if a stacktrace resource exists for this network resource.
|
||||
// The stacktrace event is expected to happen before the network
|
||||
// event so any neccesary stacktrace info should be available.
|
||||
if (this.stackTraces.has(stacktraceResourceId)) {
|
||||
const {
|
||||
stacktraceAvailable,
|
||||
lastFrame,
|
||||
targetFront,
|
||||
} = this.stackTraces.get(stacktraceResourceId);
|
||||
cause.stacktraceAvailable = stacktraceAvailable;
|
||||
cause.lastFrame = lastFrame;
|
||||
this.stackTraces.delete(channelId);
|
||||
this.stackTraces.delete(stacktraceResourceId);
|
||||
// We retrieve preliminary information about the stacktrace from the
|
||||
// NETWORK_EVENT_STACKTRACE resource via `this.stackTraces` Map,
|
||||
// The actual stacktrace is fetched lazily based on the actor id, using
|
||||
// the targetFront and the stacktrace resource id therefore we
|
||||
// map these for easy access.
|
||||
this.stackTraceRequestInfoByActorID.set(actor, {
|
||||
targetFront,
|
||||
stacktraceResourceId,
|
||||
});
|
||||
}
|
||||
|
||||
// For resources from the resource watcher cache no updates are going to be fired
|
||||
@ -388,7 +431,7 @@ class FirefoxDataProvider {
|
||||
referrerPolicy,
|
||||
blockedReason,
|
||||
blockingExtension,
|
||||
channelId,
|
||||
resourceId,
|
||||
mimeType: resource?.content?.mimeType,
|
||||
contentSize: bodySize,
|
||||
...responseProps,
|
||||
@ -638,7 +681,15 @@ class FirefoxDataProvider {
|
||||
|
||||
let response = await new Promise((resolve, reject) => {
|
||||
// Do a RDP request to fetch data from the actor.
|
||||
if (typeof this.webConsoleFront[clientMethodName] === "function") {
|
||||
if (
|
||||
clientMethodName == "getStackTrace" &&
|
||||
this.resourceWatcher.hasWatcherSupport(
|
||||
this.resourceWatcher.TYPES.NETWORK_EVENT_STACKTRACE
|
||||
)
|
||||
) {
|
||||
const requestInfo = this.stackTraceRequestInfoByActorID.get(actor);
|
||||
resolve(this._getStackTraceFromWatcher(requestInfo));
|
||||
} else if (typeof this.webConsoleFront[clientMethodName] === "function") {
|
||||
// Make sure we fetch the real actor data instead of cloned actor
|
||||
// e.g. CustomRequestPanel will clone a request with additional '-clone' actor id
|
||||
this.webConsoleFront[clientMethodName](
|
||||
|
@ -67,7 +67,7 @@ function Messages(initialState = {}) {
|
||||
const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
|
||||
|
||||
return {
|
||||
// Map with all requests (key = channelId, value = array of message objects)
|
||||
// Map with all requests (key = resourceId, value = array of message objects)
|
||||
messages: new Map(),
|
||||
messageFilterText: "",
|
||||
// Default filter type is "all",
|
||||
@ -88,14 +88,14 @@ function Messages(initialState = {}) {
|
||||
|
||||
/**
|
||||
* When a network request is selected,
|
||||
* set the current channelId affiliated with the connection.
|
||||
* set the current resourceId affiliated with the connection.
|
||||
*/
|
||||
function setCurrentChannel(state, action) {
|
||||
if (!action.request) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const { id, cause, channelId, isEventStream } = action.request;
|
||||
const { id, cause, resourceId, isEventStream } = action.request;
|
||||
const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
|
||||
let currentChannelType = null;
|
||||
let columnsKey = "columns";
|
||||
@ -113,7 +113,7 @@ function setCurrentChannel(state, action) {
|
||||
currentChannelType === state.currentChannelType
|
||||
? { ...state.columns }
|
||||
: { ...state[columnsKey] },
|
||||
currentChannelId: channelId,
|
||||
currentChannelId: resourceId,
|
||||
currentChannelType,
|
||||
currentRequestId: id,
|
||||
// Default filter text is empty string for a new connection
|
||||
|
@ -159,7 +159,7 @@ function getRequestById(state, id) {
|
||||
|
||||
function getRequestByChannelId(state, channelId) {
|
||||
return [...state.requests.requests.values()].find(
|
||||
r => r.channelId == channelId
|
||||
r => r.resourceId == channelId
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -82,11 +82,9 @@ async function performRequestAndWait(tab, monitor) {
|
||||
* Execute simple GET request
|
||||
*/
|
||||
async function performPausedRequest(connector, tab, monitor) {
|
||||
const wait = connector.connector.webConsoleFront.once("serverNetworkEvent");
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [SIMPLE_SJS], async function(
|
||||
url
|
||||
) {
|
||||
await content.wrappedJSObject.performRequests(url);
|
||||
});
|
||||
await wait;
|
||||
}
|
||||
|
@ -75,8 +75,10 @@ async function generateNetworkEventStubs() {
|
||||
for (const resource of resources) {
|
||||
if (resource.resourceType == resourceWatcher.TYPES.NETWORK_EVENT) {
|
||||
if (stacktraces.has(resource.channelId)) {
|
||||
const { stacktrace, lastFrame } = stacktraces.get(resource.channelId);
|
||||
resource.cause.stacktraceAvailable = stacktrace;
|
||||
const { stacktraceAvailable, lastFrame } = stacktraces.get(
|
||||
resource.channelId
|
||||
);
|
||||
resource.cause.stacktraceAvailable = stacktraceAvailable;
|
||||
resource.cause.lastFrame = lastFrame;
|
||||
stacktraces.delete(resource.channelId);
|
||||
}
|
||||
@ -121,6 +123,8 @@ async function generateNetworkEventStubs() {
|
||||
const updateKey = `${key} update`;
|
||||
// make sure all the updates have been happened
|
||||
if (updateCount >= noExpectedUpdates) {
|
||||
// make sure the network event stub contains all the updates
|
||||
stubs.set(key, getCleanedPacket(key, getOrderedResource(resource)));
|
||||
stubs.set(
|
||||
updateKey,
|
||||
// We cannot ensure the form of the resource, some properties
|
||||
|
@ -288,6 +288,10 @@ function getCleanedPacket(key, packet) {
|
||||
res.totalTime = existingPacket.totalTime;
|
||||
}
|
||||
|
||||
if (res.securityState && existingPacket.securityState) {
|
||||
res.securityState = existingPacket.securityState;
|
||||
}
|
||||
|
||||
if (res.actor && existingPacket.actor) {
|
||||
res.actor = existingPacket.actor;
|
||||
}
|
||||
|
@ -20,9 +20,7 @@ const {
|
||||
const rawPackets = new Map();
|
||||
rawPackets.set(`GET request`, {
|
||||
"resourceType": "network-event",
|
||||
"_type": "NetworkEvent",
|
||||
"timeStamp": 1572867483805,
|
||||
"node": null,
|
||||
"actor": "server0.conn0.netEvent4",
|
||||
"discardRequestBody": true,
|
||||
"discardResponseBody": false,
|
||||
@ -63,7 +61,6 @@ rawPackets.set(`GET request`, {
|
||||
"private": false,
|
||||
"isThirdPartyTrackingResource": false,
|
||||
"referrerPolicy": "no-referrer-when-downgrade",
|
||||
"channelId": 265845590720515,
|
||||
"updates": [
|
||||
"eventTimings",
|
||||
"requestCookies",
|
||||
@ -80,9 +77,7 @@ rawPackets.set(`GET request`, {
|
||||
|
||||
rawPackets.set(`GET request update`, {
|
||||
"resourceType": "network-event",
|
||||
"_type": "NetworkEvent",
|
||||
"timeStamp": 1572867483805,
|
||||
"node": null,
|
||||
"actor": "server0.conn0.netEvent5",
|
||||
"discardRequestBody": true,
|
||||
"discardResponseBody": false,
|
||||
@ -123,7 +118,6 @@ rawPackets.set(`GET request update`, {
|
||||
"private": false,
|
||||
"isThirdPartyTrackingResource": false,
|
||||
"referrerPolicy": "no-referrer-when-downgrade",
|
||||
"channelId": 202499118071811,
|
||||
"updates": [
|
||||
"eventTimings",
|
||||
"requestCookies",
|
||||
@ -140,9 +134,7 @@ rawPackets.set(`GET request update`, {
|
||||
|
||||
rawPackets.set(`XHR GET request`, {
|
||||
"resourceType": "network-event",
|
||||
"_type": "NetworkEvent",
|
||||
"timeStamp": 1572867483805,
|
||||
"node": null,
|
||||
"actor": "server0.conn0.netEvent21",
|
||||
"discardRequestBody": true,
|
||||
"discardResponseBody": false,
|
||||
@ -183,7 +175,6 @@ rawPackets.set(`XHR GET request`, {
|
||||
"private": false,
|
||||
"isThirdPartyTrackingResource": false,
|
||||
"referrerPolicy": "no-referrer-when-downgrade",
|
||||
"channelId": 202499118071812,
|
||||
"updates": [
|
||||
"eventTimings",
|
||||
"requestCookies",
|
||||
@ -200,9 +191,7 @@ rawPackets.set(`XHR GET request`, {
|
||||
|
||||
rawPackets.set(`XHR GET request update`, {
|
||||
"resourceType": "network-event",
|
||||
"_type": "NetworkEvent",
|
||||
"timeStamp": 1572867483805,
|
||||
"node": null,
|
||||
"actor": "server0.conn0.netEvent20",
|
||||
"discardRequestBody": true,
|
||||
"discardResponseBody": false,
|
||||
@ -258,9 +247,7 @@ rawPackets.set(`XHR GET request update`, {
|
||||
|
||||
rawPackets.set(`XHR POST request`, {
|
||||
"resourceType": "network-event",
|
||||
"_type": "NetworkEvent",
|
||||
"timeStamp": 1572867483805,
|
||||
"node": null,
|
||||
"actor": "server0.conn0.netEvent36",
|
||||
"discardRequestBody": true,
|
||||
"discardResponseBody": false,
|
||||
@ -301,7 +288,6 @@ rawPackets.set(`XHR POST request`, {
|
||||
"private": false,
|
||||
"isThirdPartyTrackingResource": false,
|
||||
"referrerPolicy": "no-referrer-when-downgrade",
|
||||
"channelId": 265845590720517,
|
||||
"updates": [
|
||||
"eventTimings",
|
||||
"requestCookies",
|
||||
@ -318,9 +304,7 @@ rawPackets.set(`XHR POST request`, {
|
||||
|
||||
rawPackets.set(`XHR POST request update`, {
|
||||
"resourceType": "network-event",
|
||||
"_type": "NetworkEvent",
|
||||
"timeStamp": 1572867483805,
|
||||
"node": null,
|
||||
"actor": "server0.conn0.netEvent36",
|
||||
"discardRequestBody": true,
|
||||
"discardResponseBody": false,
|
||||
|
@ -387,19 +387,34 @@ class WebConsoleUI {
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.NETWORK_EVENT_STACKTRACE) {
|
||||
this.netEventStackTraces.set(resource.channelId, resource);
|
||||
this.netEventStackTraces.set(resource.resourceId, resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.NETWORK_EVENT) {
|
||||
// Add the stacktrace
|
||||
if (this.netEventStackTraces.has(resource.channelId)) {
|
||||
const { stacktrace, lastFrame } = this.netEventStackTraces.get(
|
||||
resource.channelId
|
||||
);
|
||||
resource.cause.stacktraceAvailable = stacktrace;
|
||||
if (this.netEventStackTraces.has(resource.resourceId)) {
|
||||
const {
|
||||
stacktraceAvailable,
|
||||
lastFrame,
|
||||
targetFront,
|
||||
} = this.netEventStackTraces.get(resource.resourceId);
|
||||
|
||||
resource.cause.stacktraceAvailable = stacktraceAvailable;
|
||||
resource.cause.lastFrame = lastFrame;
|
||||
this.netEventStackTraces.delete(resource.channelId);
|
||||
this.netEventStackTraces.delete(resource.resourceId);
|
||||
|
||||
if (
|
||||
this.wrapper?.networkDataProvider?.stackTraceRequestInfoByActorID
|
||||
) {
|
||||
this.wrapper.networkDataProvider.stackTraceRequestInfoByActorID.set(
|
||||
resource.actor,
|
||||
{
|
||||
targetFront,
|
||||
resourceId: resource.resourceId,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ class WebConsoleWrapper {
|
||||
* @param {WebConsoleUI} webConsoleUI
|
||||
* @param {Toolbox} toolbox
|
||||
* @param {Document} document
|
||||
*
|
||||
*/
|
||||
constructor(parentNode, webConsoleUI, toolbox, document) {
|
||||
EventEmitter.decorate(this);
|
||||
@ -89,6 +90,7 @@ class WebConsoleWrapper {
|
||||
updateRequest: (id, data) => this.batchedRequestUpdates({ id, data }),
|
||||
},
|
||||
webConsoleFront,
|
||||
resourceWatcher: this.hud.resourceWatcher,
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
|
@ -17,5 +17,6 @@ DevToolsModules(
|
||||
'network-observer.js',
|
||||
'network-response-listener.js',
|
||||
'stack-trace-collector.js',
|
||||
'stack-traces-actor.js',
|
||||
'websocket-actor.js',
|
||||
)
|
||||
|
@ -70,10 +70,12 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
|
||||
this._isXHR = networkEvent.isXHR;
|
||||
|
||||
this._cause = networkEvent.cause;
|
||||
this._cause.stacktraceAvailable = !!(
|
||||
this._stackTrace &&
|
||||
(typeof this._stackTrace == "boolean" || this._stackTrace.length)
|
||||
);
|
||||
// Lets remove the last frame here as
|
||||
// it is passed from the the server by the NETWORK_EVENT_STACKTRACE
|
||||
// resource type. This done here for backward compatibility.
|
||||
if (this._cause.lastFrame) {
|
||||
delete this._cause.lastFrame;
|
||||
}
|
||||
|
||||
this._fromCache = networkEvent.fromCache;
|
||||
this._fromServiceWorker = networkEvent.fromServiceWorker;
|
||||
@ -81,7 +83,7 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
|
||||
networkEvent.isThirdPartyTrackingResource;
|
||||
this._referrerPolicy = networkEvent.referrerPolicy;
|
||||
this._channelId = networkEvent.channelId;
|
||||
|
||||
this._serial = networkEvent.serial;
|
||||
this._blockedReason = networkEvent.blockedReason;
|
||||
this._blockingExtension = networkEvent.blockingExtension;
|
||||
|
||||
@ -119,7 +121,9 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
|
||||
referrerPolicy: this._referrerPolicy,
|
||||
blockedReason: this._blockedReason,
|
||||
blockingExtension: this._blockingExtension,
|
||||
channelId: this._channelId,
|
||||
// For websocket requests the serial is used instead of the channel id.
|
||||
stacktraceResourceId:
|
||||
this._cause.type == "websocket" ? this._serial : this._channelId,
|
||||
updates: [],
|
||||
};
|
||||
},
|
||||
@ -260,23 +264,6 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* The "getStackTrace" packet type handler.
|
||||
*
|
||||
* @return object
|
||||
* The response packet - stack trace.
|
||||
*/
|
||||
async getStackTrace() {
|
||||
const stacktrace = this._stackTrace;
|
||||
if (stacktrace && typeof stacktrace == "boolean") {
|
||||
this._stackTrace = [];
|
||||
}
|
||||
|
||||
return {
|
||||
stacktrace,
|
||||
};
|
||||
},
|
||||
|
||||
/** ****************************************************************
|
||||
* Listeners for new network event data coming from NetworkMonitor.
|
||||
******************************************************************/
|
||||
@ -491,10 +478,8 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
|
||||
if (this.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onEventUpdate("responseCache", {
|
||||
responseCache: content.responseCache,
|
||||
});
|
||||
this._response.responseCache = content.responseCache;
|
||||
this._onEventUpdate("responseCache", {});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -837,6 +837,7 @@ NetworkObserver.prototype = {
|
||||
}
|
||||
if (wsChannel) {
|
||||
event.url = wsChannel.URI.spec;
|
||||
event.serial = wsChannel.serial;
|
||||
}
|
||||
}
|
||||
|
||||
|
59
devtools/server/actors/network-monitor/stack-traces-actor.js
Normal file
59
devtools/server/actors/network-monitor/stack-traces-actor.js
Normal file
@ -0,0 +1,59 @@
|
||||
/* 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 { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
|
||||
const { stackTracesSpec } = require("devtools/shared/specs/stacktraces");
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"WebConsoleUtils",
|
||||
"devtools/server/actors/webconsole/utils",
|
||||
true
|
||||
);
|
||||
|
||||
const {
|
||||
TYPES: { NETWORK_EVENT_STACKTRACE },
|
||||
getResourceWatcher,
|
||||
} = require("devtools/server/actors/resources/index");
|
||||
|
||||
/**
|
||||
* Manages the stacktraces of the network
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
*/
|
||||
const StackTracesActor = ActorClassWithSpec(stackTracesSpec, {
|
||||
initialize(conn, targetActor) {
|
||||
Actor.prototype.initialize.call(this, conn);
|
||||
this.targetActor = targetActor;
|
||||
},
|
||||
|
||||
destroy(conn) {
|
||||
Actor.prototype.destroy.call(this, conn);
|
||||
},
|
||||
|
||||
/**
|
||||
* The "getStackTrace" packet type handler.
|
||||
*
|
||||
* @return object
|
||||
* The response packet - stack trace.
|
||||
*/
|
||||
getStackTrace(resourceId) {
|
||||
const networkEventStackTraceWatcher = getResourceWatcher(
|
||||
this.targetActor,
|
||||
NETWORK_EVENT_STACKTRACE
|
||||
);
|
||||
if (!networkEventStackTraceWatcher) {
|
||||
throw new Error("Not listening for network event stacktraces");
|
||||
}
|
||||
const stacktrace = networkEventStackTraceWatcher.getStackTrace(resourceId);
|
||||
return {
|
||||
stacktrace: WebConsoleUtils.removeFramesAboveDebuggerEval(stacktrace),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
exports.StackTracesActor = StackTracesActor;
|
@ -15,6 +15,7 @@ const TYPES = {
|
||||
PLATFORM_MESSAGE: "platform-message",
|
||||
NETWORK_EVENT: "network-event",
|
||||
STYLESHEET: "stylesheet",
|
||||
NETWORK_EVENT_STACKTRACE: "network-event-stacktrace",
|
||||
};
|
||||
exports.TYPES = TYPES;
|
||||
|
||||
@ -49,6 +50,9 @@ const FrameTargetResources = augmentResourceDictionary({
|
||||
[TYPES.STYLESHEET]: {
|
||||
path: "devtools/server/actors/resources/stylesheets",
|
||||
},
|
||||
[TYPES.NETWORK_EVENT_STACKTRACE]: {
|
||||
path: "devtools/server/actors/resources/network-events-stacktraces",
|
||||
},
|
||||
});
|
||||
const ParentProcessResources = augmentResourceDictionary({
|
||||
[TYPES.NETWORK_EVENT]: {
|
||||
|
@ -15,6 +15,7 @@ DevToolsModules(
|
||||
'document-event.js',
|
||||
'error-messages.js',
|
||||
'index.js',
|
||||
'network-events-stacktraces.js',
|
||||
'network-events.js',
|
||||
'platform-messages.js',
|
||||
'stylesheets.js',
|
||||
|
203
devtools/server/actors/resources/network-events-stacktraces.js
Normal file
203
devtools/server/actors/resources/network-events-stacktraces.js
Normal file
@ -0,0 +1,203 @@
|
||||
/* 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 {
|
||||
TYPES: { NETWORK_EVENT_STACKTRACE },
|
||||
} = require("devtools/server/actors/resources/index");
|
||||
|
||||
const { Ci, components } = require("chrome");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"ChannelEventSinkFactory",
|
||||
"devtools/server/actors/network-monitor/channel-event-sink",
|
||||
true
|
||||
);
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"matchRequest",
|
||||
"devtools/server/actors/network-monitor/network-observer",
|
||||
true
|
||||
);
|
||||
|
||||
class NetworkEventStackTracesWatcher {
|
||||
/**
|
||||
* Start watching for all network event's stack traces related to a given Target actor.
|
||||
*
|
||||
* @param TargetActor targetActor
|
||||
* The target actor from which we should observe the strack traces
|
||||
* @param Object options
|
||||
* Dictionary object with following attributes:
|
||||
* - onAvailable: mandatory
|
||||
* This will be called for each resource.
|
||||
*/
|
||||
async watch(targetActor, { onAvailable }) {
|
||||
Services.obs.addObserver(this, "http-on-opening-request");
|
||||
Services.obs.addObserver(this, "document-on-opening-request");
|
||||
Services.obs.addObserver(this, "network-monitor-alternate-stack");
|
||||
ChannelEventSinkFactory.getService().registerCollector(this);
|
||||
|
||||
this.targetActor = targetActor;
|
||||
this.onStackTraceAvailable = onAvailable;
|
||||
|
||||
this.stacktraces = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching for network event's strack traces related to a given Target Actor.
|
||||
*
|
||||
* @param TargetActor targetActor
|
||||
* The target actor from which we should stop observing the strack traces
|
||||
*/
|
||||
destroy(targetActor) {
|
||||
Services.obs.removeObserver(this, "http-on-opening-request");
|
||||
Services.obs.removeObserver(this, "document-on-opening-request");
|
||||
Services.obs.removeObserver(this, "network-monitor-alternate-stack");
|
||||
ChannelEventSinkFactory.getService().unregisterCollector(this);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const oldId = oldChannel.channelId;
|
||||
const stacktrace = this.stacktraces.get(oldId);
|
||||
if (stacktrace) {
|
||||
this._setStackTrace(newChannel.channelId, stacktrace);
|
||||
}
|
||||
}
|
||||
|
||||
observe(subject, topic, data) {
|
||||
let channel, id;
|
||||
try {
|
||||
// We need to QI nsIHttpChannel in order to load the interface's
|
||||
// methods / attributes for later code that could assume we are dealing
|
||||
// with a nsIHttpChannel.
|
||||
channel = subject.QueryInterface(Ci.nsIHttpChannel);
|
||||
id = channel.channelId;
|
||||
} catch (e1) {
|
||||
try {
|
||||
channel = subject.QueryInterface(Ci.nsIIdentChannel);
|
||||
id = channel.channelId;
|
||||
} catch (e2) {
|
||||
// WebSocketChannels do not have IDs, so use the serial. When a WebSocket is
|
||||
// opened in a content process, a channel is created locally but the HTTP
|
||||
// channel for the connection lives entirely in the parent process. When
|
||||
// the server code running in the parent sees that HTTP channel, it will
|
||||
// look for the creation stack using the websocket's serial.
|
||||
try {
|
||||
channel = subject.QueryInterface(Ci.nsIWebSocketChannel);
|
||||
id = channel.serial;
|
||||
} catch (e3) {
|
||||
// Channels which don't implement the above interfaces can appear here,
|
||||
// such as nsIFileChannel. Ignore these channels.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: is window the best filter to use?
|
||||
if (!matchRequest(channel, { window: this.targetActor.window })) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.stacktraces.has(id)) {
|
||||
// We can get up to two stack traces for the same channel: one each from
|
||||
// the two observer topics we are listening to. Use the first stack trace
|
||||
// which is specified, and ignore any later one.
|
||||
return;
|
||||
}
|
||||
|
||||
const stacktrace = [];
|
||||
switch (topic) {
|
||||
case "http-on-opening-request":
|
||||
case "document-on-opening-request": {
|
||||
// The channel is being opened on the main thread, associate the current
|
||||
// stack with it.
|
||||
//
|
||||
// Convert the nsIStackFrame XPCOM objects to a nice JSON that can be
|
||||
// passed around through message managers etc.
|
||||
let frame = components.stack;
|
||||
if (frame?.caller) {
|
||||
frame = frame.caller;
|
||||
while (frame) {
|
||||
stacktrace.push({
|
||||
filename: frame.filename,
|
||||
lineNumber: frame.lineNumber,
|
||||
columnNumber: frame.columnNumber,
|
||||
functionName: frame.name,
|
||||
asyncCause: frame.asyncCause,
|
||||
});
|
||||
frame = frame.caller || frame.asyncCaller;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "network-monitor-alternate-stack": {
|
||||
// An alternate stack trace is being specified for this channel.
|
||||
// The topic data is the JSON for the saved frame stack we should use,
|
||||
// so convert this into the expected format.
|
||||
//
|
||||
// This topic is used in the following cases:
|
||||
//
|
||||
// - The HTTP channel is opened asynchronously or on a different thread
|
||||
// from the code which triggered its creation, in which case the stack
|
||||
// from components.stack will be empty. The alternate stack will be
|
||||
// for the point we want to associate with the channel.
|
||||
//
|
||||
// - The channel is not a nsIHttpChannel, and we will receive no
|
||||
// opening request notification for it.
|
||||
let frame = JSON.parse(data);
|
||||
while (frame) {
|
||||
stacktrace.push({
|
||||
filename: frame.source,
|
||||
lineNumber: frame.line,
|
||||
columnNumber: frame.column,
|
||||
functionName: frame.functionDisplayName,
|
||||
asyncCause: frame.asyncCause,
|
||||
});
|
||||
frame = frame.parent || frame.asyncParent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error("Unexpected observe() topic");
|
||||
}
|
||||
|
||||
this._setStackTrace(id, stacktrace);
|
||||
}
|
||||
|
||||
_setStackTrace(resourceId, stacktrace) {
|
||||
this.stacktraces.set(resourceId, stacktrace);
|
||||
this.onStackTraceAvailable([
|
||||
{
|
||||
resourceType: NETWORK_EVENT_STACKTRACE,
|
||||
resourceId,
|
||||
targetFront: this.targetFront,
|
||||
stacktraceAvailable: stacktrace && stacktrace.length > 0,
|
||||
lastFrame:
|
||||
stacktrace && stacktrace.length > 0 ? stacktrace[0] : undefined,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
getStackTrace(id) {
|
||||
let stacktrace = [];
|
||||
if (this.stacktraces.has(id)) {
|
||||
stacktrace = this.stacktraces.get(id);
|
||||
this.stacktraces.delete(id);
|
||||
}
|
||||
return stacktrace;
|
||||
}
|
||||
}
|
||||
module.exports = NetworkEventStackTracesWatcher;
|
@ -30,15 +30,12 @@ class NetworkEventWatcher {
|
||||
* This will be called for each resource.
|
||||
* - onUpdated: optional function
|
||||
* This would be called multiple times for each resource.
|
||||
* - onDestroyed: optional function
|
||||
* This would be called multiple times for each resource.
|
||||
*/
|
||||
async watch(watcherActor, { onAvailable, onUpdated, onDestroyed }) {
|
||||
async watch(watcherActor, { onAvailable, onUpdated }) {
|
||||
this.networkEvents = new Map();
|
||||
this.watcherActor = watcherActor;
|
||||
this.onNetworkEventAvailable = onAvailable;
|
||||
this.onNetworkEventUpdated = onUpdated;
|
||||
this.onNeworkEventDestroyed = onDestroyed;
|
||||
|
||||
this.listener = new NetworkObserver(
|
||||
{ browserId: watcherActor.browserId },
|
||||
@ -58,7 +55,7 @@ class NetworkEventWatcher {
|
||||
this,
|
||||
{
|
||||
onNetworkEventUpdate: this.onNetworkEventUpdate.bind(this),
|
||||
onNetworkEventDestroy: this.onNeworkEventDestroyed,
|
||||
onNetworkEventDestroy: this.onNetworkEventDestroy.bind(this),
|
||||
},
|
||||
event
|
||||
);
|
||||
@ -141,6 +138,12 @@ class NetworkEventWatcher {
|
||||
]);
|
||||
}
|
||||
|
||||
onNetworkEventDestroy(channelId) {
|
||||
if (this.networkEvents.has(channelId)) {
|
||||
this.networkEvents.delete(channelId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching for network event related to a given Watcher Actor.
|
||||
*
|
||||
|
@ -275,6 +275,14 @@ const ActorRegistry = {
|
||||
constructor: "ManifestActor",
|
||||
type: { target: true },
|
||||
});
|
||||
this.registerModule(
|
||||
"devtools/server/actors/network-monitor/stack-traces-actor",
|
||||
{
|
||||
prefix: "stacktraces",
|
||||
constructor: "StackTracesActor",
|
||||
type: { target: true },
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -117,6 +117,8 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
|
||||
[Resources.TYPES.PLATFORM_MESSAGE]: true,
|
||||
[Resources.TYPES.NETWORK_EVENT]:
|
||||
enableServerWatcher && hasBrowserElement,
|
||||
[Resources.TYPES.NETWORK_EVENT_STACKTRACE]:
|
||||
enableServerWatcher && hasBrowserElement,
|
||||
[Resources.TYPES.STYLESHEET]:
|
||||
enableServerWatcher && hasBrowserElement,
|
||||
},
|
||||
|
@ -19,8 +19,8 @@ module.exports = async function({
|
||||
onAvailable([
|
||||
{
|
||||
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT_STACKTRACE,
|
||||
channelId: actor.channelId,
|
||||
stacktrace: actor.cause.stacktraceAvailable,
|
||||
resourceId: actor.channelId,
|
||||
stacktraceAvailable: actor.cause.stacktraceAvailable,
|
||||
lastFrame: actor.cause.lastFrame,
|
||||
},
|
||||
]);
|
||||
|
@ -39,9 +39,7 @@ module.exports = async function({
|
||||
const resource = {
|
||||
resourceId: actor.channelId,
|
||||
resourceType: ResourceWatcher.TYPES.NETWORK_EVENT,
|
||||
_type: "NetworkEvent",
|
||||
timeStamp: actor.timeStamp,
|
||||
node: null,
|
||||
actor: actor.actor,
|
||||
discardRequestBody: true,
|
||||
discardResponseBody: true,
|
||||
@ -61,7 +59,10 @@ module.exports = async function({
|
||||
referrerPolicy: actor.referrerPolicy,
|
||||
blockedReason: actor.blockedReason,
|
||||
blockingExtension: actor.blockingExtension,
|
||||
channelId: actor.channelId,
|
||||
stacktraceResourceId:
|
||||
actor.cause.type == "websocket"
|
||||
? actor.url.replace(/^http/, "ws")
|
||||
: actor.channelId,
|
||||
updates: [],
|
||||
};
|
||||
|
||||
|
@ -280,7 +280,7 @@ class ResourceWatcher {
|
||||
// ...request existing resource and new one to come from this one target
|
||||
// *but* only do that for backward compat, where we don't have the watcher API
|
||||
// (See bug 1626647)
|
||||
if (this._hasWatcherSupport(resourceType)) {
|
||||
if (this.hasWatcherSupport(resourceType)) {
|
||||
continue;
|
||||
}
|
||||
await this._watchResourcesForTarget(targetFront, resourceType);
|
||||
@ -601,7 +601,7 @@ class ResourceWatcher {
|
||||
);
|
||||
}
|
||||
|
||||
_hasWatcherSupport(resourceType) {
|
||||
hasWatcherSupport(resourceType) {
|
||||
return this.watcher?.traits?.resources?.[resourceType];
|
||||
}
|
||||
|
||||
@ -632,7 +632,7 @@ class ResourceWatcher {
|
||||
|
||||
// If the server supports the Watcher API and the Watcher supports
|
||||
// this resource type, use this API
|
||||
if (this._hasWatcherSupport(resourceType)) {
|
||||
if (this.hasWatcherSupport(resourceType)) {
|
||||
await this.watcher.watchResources([resourceType]);
|
||||
return;
|
||||
}
|
||||
@ -712,7 +712,7 @@ class ResourceWatcher {
|
||||
|
||||
// If the server supports the Watcher API and the Watcher supports
|
||||
// this resource type, use this API
|
||||
if (this._hasWatcherSupport(resourceType)) {
|
||||
if (this.hasWatcherSupport(resourceType)) {
|
||||
this.watcher.unwatchResources([resourceType]);
|
||||
return;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ const TEST_URI = `${URL_ROOT_SSL}network_document.html`;
|
||||
const REQUEST_STUB = {
|
||||
code: `await fetch("/request_post_0.html", { method: "POST" });`,
|
||||
expected: {
|
||||
stacktrace: true,
|
||||
stacktraceAvailable: true,
|
||||
lastFrame: {
|
||||
filename:
|
||||
"https://example.com/browser/devtools/shared/resources/tests/network_document.html",
|
||||
|
@ -209,6 +209,11 @@ const Types = (exports.__TypesForTests = [
|
||||
spec: "devtools/shared/specs/source",
|
||||
front: "devtools/client/fronts/source",
|
||||
},
|
||||
{
|
||||
types: ["stacktraces"],
|
||||
spec: "devtools/shared/specs/stacktraces",
|
||||
front: "devtools/client/fronts/stacktraces",
|
||||
},
|
||||
{
|
||||
types: [
|
||||
"cookies",
|
||||
|
@ -45,6 +45,7 @@ DevToolsModules(
|
||||
'root.js',
|
||||
'screenshot.js',
|
||||
'source.js',
|
||||
'stacktraces.js',
|
||||
'storage.js',
|
||||
'string.js',
|
||||
'styles.js',
|
||||
|
20
devtools/shared/specs/stacktraces.js
Normal file
20
devtools/shared/specs/stacktraces.js
Normal file
@ -0,0 +1,20 @@
|
||||
/* 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 { generateActorSpec, RetVal, Arg } = require("devtools/shared/protocol");
|
||||
|
||||
const stackTracesSpec = generateActorSpec({
|
||||
typeName: "stacktraces",
|
||||
methods: {
|
||||
getStackTrace: {
|
||||
request: { resourceId: Arg(0) },
|
||||
// stacktrace is an "array:string", but not always.
|
||||
response: RetVal("json"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
exports.stackTracesSpec = stackTracesSpec;
|
Loading…
Reference in New Issue
Block a user