Backed out 9 changesets (bug 1444132) for bc failures on browser_ext_devtools_network. CLOSED TREE

Backed out changeset e702d9a79546 (bug 1444132)
Backed out changeset 22470174dfa0 (bug 1444132)
Backed out changeset 793019c3fd49 (bug 1444132)
Backed out changeset 6afaf8f9db5e (bug 1444132)
Backed out changeset 862a9a773767 (bug 1444132)
Backed out changeset c33d134af968 (bug 1444132)
Backed out changeset 526c4eaaac80 (bug 1444132)
Backed out changeset 09b0af4bda95 (bug 1444132)
Backed out changeset 5fe879d4e4cb (bug 1444132)
This commit is contained in:
Narcis Beleuzu 2018-07-31 16:18:41 +03:00
parent 771590fdf4
commit 050b31f851
13 changed files with 559 additions and 439 deletions

View File

@ -196,7 +196,7 @@ function requestsReducer(state = Requests(), action) {
* Remove the currently selected custom request.
*/
function closeCustomRequest(state) {
const { requests, selectedId, preselectedId } = state;
const { requests, selectedId } = state;
if (!selectedId) {
return state;
@ -209,14 +209,10 @@ function closeCustomRequest(state) {
return state;
}
// If the custom request is already in the Map, select it immediately,
// and reset `preselectedId` attribute.
const hasPreselectedId = preselectedId && requests.has(preselectedId);
return {
...state,
requests: mapDelete(requests, selectedId),
preselectedId: hasPreselectedId ? null : preselectedId,
selectedId: hasPreselectedId ? preselectedId : null,
requests: mapDelete(state.requests, selectedId),
selectedId: null,
};
}

View File

@ -109,7 +109,7 @@ add_task(async function() {
is(store.getState().requests.requests.size, EXPECTED_REQUESTS.length,
"All the page events should be recorded.");
EXPECTED_REQUESTS.forEach((spec, i) => {
EXPECTED_REQUESTS.forEach(async (spec, i) => {
const { method, url, causeType, causeUri, stack } = spec;
const requestItem = getSortedRequests(store.getState()).get(i);
@ -125,6 +125,8 @@ add_task(async function() {
const stacktrace = requestItem.stacktrace;
const stackLen = stacktrace ? stacktrace.length : 0;
await waitUntil(() => !!requestItem.stacktrace);
if (stack) {
ok(stacktrace, `Request #${i} has a stacktrace`);
ok(stackLen > 0,

View File

@ -10,13 +10,12 @@
add_task(async function() {
const EXPECTED_REQUESTS = [
// Request to HTTP URL, redirects to HTTPS
{ status: 302 },
// Serves HTTPS, sets the Strict-Transport-Security header
// This request is the redirection caused by the first one
{ status: 200 },
// Request to HTTP URL, redirects to HTTPS, has callstack
{ status: 302, hasStack: true },
// Serves HTTPS, sets the Strict-Transport-Security header, no stack
{ status: 200, hasStack: false },
// Second request to HTTP redirects to HTTPS internally
{ status: 200 },
{ status: 200, hasStack: true },
];
const { tab, monitor } = await initNetMonitor(CUSTOM_GET_URL);
@ -40,7 +39,7 @@ add_task(async function() {
await Promise.all(requests);
EXPECTED_REQUESTS.forEach(({status}, i) => {
EXPECTED_REQUESTS.forEach(({status, hasStack}, i) => {
const item = getSortedRequests(store.getState()).get(i);
is(item.status, status, `Request #${i} has the expected status`);
@ -48,8 +47,12 @@ add_task(async function() {
const { stacktrace } = item;
const stackLen = stacktrace ? stacktrace.length : 0;
ok(stacktrace, `Request #${i} has a stacktrace`);
ok(stackLen > 0, `Request #${i} has a stacktrace with ${stackLen} items`);
if (hasStack) {
ok(stacktrace, `Request #${i} has a stacktrace`);
ok(stackLen > 0, `Request #${i} has a stacktrace with ${stackLen} items`);
} else {
is(stackLen, 0, `Request #${i} has an empty stacktrace`);
}
});
// Send a request to reset the HSTS policy to state before the test

View File

@ -61,8 +61,8 @@ add_task(async function() {
await waitUntil(() => {
sentItem = getSelectedRequest(store.getState());
origItem = getSortedRequests(store.getState()).get(0);
return sentItem && sentItem.requestHeaders && sentItem.requestPostData &&
origItem && origItem.requestHeaders && origItem.requestPostData;
return sentItem.requestHeaders && sentItem.requestPostData &&
origItem.requestHeaders && origItem.requestPostData;
});
await testSentRequest(sentItem, origItem);

View File

@ -29,15 +29,13 @@ add_task(async function() {
await performRequests(monitor, tab, 2);
info("Clicking stack-trace tab and waiting for stack-trace panel to open");
const waitForTab = waitForDOM(document, "#stack-trace-tab");
const wait = waitForDOM(document, "#stack-trace-panel .frame-link", 5);
// Click on the first request
EventUtils.sendMouseEvent({ type: "mousedown" },
document.querySelector(".request-list-item"));
await waitForTab;
const waitForPanel = waitForDOM(document, "#stack-trace-panel .frame-link", 5);
// Open the stack-trace tab for that request
document.getElementById("stack-trace-tab").click();
await waitForPanel;
await wait;
const frameLinkNode = document.querySelector(".frame-link");
await checkClickOnNode(toolbox, frameLinkNode);

View File

@ -44,7 +44,6 @@ DevToolsModules(
'layout.js',
'memory.js',
'network-event.js',
'network-monitor.js',
'object.js',
'pause-scoped.js',
'perf.js',

View File

@ -12,16 +12,16 @@ const { LongStringActor } = require("devtools/server/actors/string");
* Creates an actor for a network event.
*
* @constructor
* @param object netMonitorActor
* The parent NetworkMonitorActor instance for this object.
* @param object webConsoleActor
* The parent WebConsoleActor instance for this object.
*/
const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
initialize(netMonitorActor) {
initialize(webConsoleActor) {
// Necessary to get the events to work
protocol.Actor.prototype.initialize.call(this, netMonitorActor.conn);
protocol.Actor.prototype.initialize.call(this, webConsoleActor.conn);
this.netMonitorActor = netMonitorActor;
this.conn = this.netMonitorActor.conn;
this.webConsoleActor = webConsoleActor;
this.conn = this.webConsoleActor.conn;
this._request = {
method: null,
@ -72,16 +72,22 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
* Releases this actor from the pool.
*/
destroy(conn) {
if (!this.netMonitorActor) {
if (!this.webConsoleActor) {
return;
}
if (this._request.url) {
this.netMonitorActor._networkEventActorsByURL.delete(this._request.url);
this.webConsoleActor._networkEventActorsByURL.delete(this._request.url);
}
if (this.channel) {
this.netMonitorActor._netEvents.delete(this.channel);
this.webConsoleActor._netEvents.delete(this.channel);
}
// Nullify webConsoleActor before calling releaseActor as it will recall this method
// To be removed once WebConsoleActor switches to protocol.js
const actor = this.webConsoleActor;
this.webConsoleActor = null;
actor.releaseActor(this);
protocol.Actor.prototype.destroy.call(this, conn);
},
@ -102,17 +108,14 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
this._cause = networkEvent.cause;
this._fromCache = networkEvent.fromCache;
this._fromServiceWorker = networkEvent.fromServiceWorker;
this._channelId = networkEvent.channelId;
// Stack trace info isn't sent automatically. The client
// needs to request it explicitly using getStackTrace
// packet. NetmonitorActor may pass just a boolean instead of the stack
// when the actor is in parent process and stack is in the content process.
// packet.
this._stackTrace = networkEvent.cause.stacktrace;
delete networkEvent.cause.stacktrace;
networkEvent.cause.stacktraceAvailable =
!!(this._stackTrace &&
(typeof this._stackTrace == "boolean" || this._stackTrace.length));
!!(this._stackTrace && this._stackTrace.length);
for (const prop of ["method", "url", "httpVersion", "headersSize"]) {
this._request[prop] = networkEvent[prop];
@ -248,29 +251,9 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
* @return object
* The response packet - stack trace.
*/
async getStackTrace() {
let stacktrace = this._stackTrace;
// If _stackTrace was "true", it means we are in parent process
// and the stack is available from the content process.
// Fetch it lazily from here via the message manager.
if (stacktrace && typeof stacktrace == "boolean") {
const messageManager = this.netMonitorActor.messageManager;
stacktrace = await new Promise(resolve => {
const onMessage = ({ data }) => {
const { channelId, stack } = data;
if (channelId == this._channelId) {
messageManager.removeMessageListener("debug:request-stack", onMessage);
resolve(stack);
}
};
messageManager.addMessageListener("debug:request-stack", onMessage);
messageManager.sendAsyncMessage("debug:request-stack", this._channelId);
});
this._stackTrace = stacktrace;
}
getStackTrace() {
return {
stacktrace,
stacktrace: this._stackTrace,
};
},

View File

@ -1,202 +0,0 @@
/* 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 { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
const { networkMonitorSpec } = require("devtools/shared/specs/network-monitor");
loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "NetworkEventActor", "devtools/server/actors/network-event", true);
const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
// Imported from WebConsole actor
_netEvents: new Map(),
_networkEventActorsByURL: new Map(),
/**
* NetworkMonitorActor is instanciated from WebConsoleActor.startListeners
* Either in the same process, for debugging service worker requests or when debugger
* the parent process itself and tracking chrome requests.
* Or in another process, for tracking content requests that are actually done in the
* parent process.
*
* @param object filters
* Contains an `outerWindowID` attribute when this is used across processes.
* Or a `window` attribute when instanciated in the same process.
* @param number parentID (optional)
* To be removed, specify the ID of the Web console actor.
* This is used to fake emitting an event from it to prevent changing RDP
* behavior.
* @param nsIMessageManager messageManager (optional)
* Passed only when it is instanciated across processes. This is the manager to
* use to communicate with the other process.
* @param object stackTraceCollector (optional)
* When the actor runs in the same process than the requests we are inspecting,
* the web console actor hands over a shared instance to the stack trace
* collector.
*/
initialize(conn, filters, parentID, messageManager, stackTraceCollector) {
Actor.prototype.initialize.call(this, conn);
this.parentID = parentID;
this.messageManager = messageManager;
this.stackTraceCollector = stackTraceCollector;
// Immediately start watching for new request according to `filters`.
// NetworkMonitor will call `onNetworkEvent` method.
this.netMonitor = new NetworkMonitor(filters, this);
this.netMonitor.init();
if (this.messageManager) {
this.stackTraces = new Set();
this.onStackTraceAvailable = this.onStackTraceAvailable.bind(this);
this.messageManager.addMessageListener("debug:request-stack-available",
this.onStackTraceAvailable);
this.onRequestContent = this.onRequestContent.bind(this);
this.messageManager.addMessageListener("debug:request-content",
this.onRequestContent);
this.onSetPreference = this.onSetPreference.bind(this);
this.messageManager.addMessageListener("debug:netmonitor-preference",
this.onSetPreference);
this.onGetNetworkEventActor = this.onGetNetworkEventActor.bind(this);
this.messageManager.addMessageListener("debug:get-network-event-actor",
this.onGetNetworkEventActor);
this.destroy = this.destroy.bind(this);
this.messageManager.addMessageListener("debug:destroy-network-monitor",
this.destroy);
}
},
destroy() {
Actor.prototype.destroy.call(this);
if (this.netMonitor) {
this.netMonitor.destroy();
this.netMonitor = null;
}
if (this.messageManager) {
this.stackTraces.clear();
this.messageManager.removeMessageListener("debug:request-stack-available",
this.onStackTraceAvailable);
this.messageManager.removeMessageListener("debug:request-content",
this.onRequestContent);
this.messageManager.removeMessageListener("debug:netmonitor-preference",
this.onSetPreference);
this.messageManager.removeMessageListener("debug:get-network-event-actor",
this.onGetNetworkEventActor);
this.messageManager.removeMessageListener("debug:destroy-network-monitor",
this.destroy);
this.messageManager = null;
}
},
onStackTraceAvailable(msg) {
const { channelId } = msg.data;
if (!msg.data.stacktrace) {
this.stackTraces.delete(channelId);
} else {
this.stackTraces.add(channelId);
}
},
getRequestContentForURL(url) {
const actor = this._networkEventActorsByURL.get(url);
if (!actor) {
return null;
}
const content = actor._response.content;
if (actor._discardResponseBody || actor._truncated || !content || !content.size) {
// Do not return the stylesheet text if there is no meaningful content or if it's
// still loading. Let the caller handle it by doing its own separate request.
return null;
}
if (content.text.type != "longString") {
// For short strings, the text is available directly.
return {
content: content.text,
contentType: content.mimeType,
};
}
// For long strings, look up the actor that holds the full text.
const longStringActor = this.conn._getOrCreateActor(content.text.actor);
if (!longStringActor) {
return null;
}
return {
content: longStringActor.str,
contentType: content.mimeType,
};
},
onRequestContent(msg) {
const { url } = msg.data;
const content = this.getRequestContentForURL(url);
this.messageManager.sendAsyncMessage("debug:request-content", {
url,
content,
});
},
onSetPreference({ data }) {
if ("saveRequestAndResponseBodies" in data) {
this.netMonitor.saveRequestAndResponseBodies = data.saveRequestAndResponseBodies;
}
if ("throttleData" in data) {
this.netMonitor.throttleData = data.throttleData;
}
},
onGetNetworkEventActor({ data }) {
const actor = this.getNetworkEventActor(data.channelId);
this.messageManager.sendAsyncMessage("debug:get-network-event-actor", actor.form());
},
getNetworkEventActor(channelId) {
let actor = this._netEvents.get(channelId);
if (actor) {
return actor;
}
actor = new NetworkEventActor(this);
this.manage(actor);
// map channel to actor so we can associate future events with it
this._netEvents.set(channelId, actor);
return actor;
},
// This method is called by NetworkMonitor instance when a new request is fired
onNetworkEvent(event) {
const { channelId } = event;
const actor = this.getNetworkEventActor(channelId);
this._netEvents.set(channelId, actor);
if (this.messageManager) {
event.cause.stacktrace = this.stackTraces.has(channelId);
if (event.cause.stacktrace) {
this.stackTraces.delete(channelId);
}
} else {
event.cause.stacktrace = this.stackTraceCollector.getStackTrace(channelId);
}
actor.init(event);
this._networkEventActorsByURL.set(actor._request.url, actor);
const packet = {
from: this.parentID,
type: "networkEvent",
eventActor: actor.form()
};
this.conn.send(packet);
return actor;
},
});
exports.NetworkMonitorActor = NetworkMonitorActor;

View File

@ -142,6 +142,46 @@ function getSheetText(sheet, consoleActor) {
exports.getSheetText = getSheetText;
/**
* Try to fetch the stylesheet text from the network monitor. If it was enabled during
* the load, it should have a copy of the text saved.
*
* @param string href
* The URL of the sheet to fetch.
*/
function fetchStylesheetFromNetworkMonitor(href, consoleActor) {
if (!consoleActor) {
return null;
}
const request = consoleActor.getNetworkEventActorForURL(href);
if (!request) {
return null;
}
const content = request._response.content;
if (request._discardResponseBody || request._truncated || !content || !content.size) {
// Do not return the stylesheet text if there is no meaningful content or if it's
// still loading. Let the caller handle it by doing its own separate request.
return null;
}
if (content.text.type != "longString") {
// For short strings, the text is available directly.
return {
content: content.text,
contentType: content.mimeType,
};
}
// For long strings, look up the actor that holds the full text.
const longStringActor = consoleActor.conn._getOrCreateActor(content.text.actor);
if (!longStringActor) {
return null;
}
return {
content: longStringActor.str,
contentType: content.mimeType,
};
}
/**
* Get the charset of the stylesheet.
*/
@ -177,12 +217,9 @@ function getCSSCharset(sheet) {
async function fetchStylesheet(sheet, consoleActor) {
const href = sheet.href;
let result;
if (consoleActor) {
result = await consoleActor.getRequestContentForURL(href);
if (result) {
return result;
}
let result = fetchStylesheetFromNetworkMonitor(href, consoleActor);
if (result) {
return result;
}
const options = {

View File

@ -19,7 +19,9 @@ const { createValueGrip, stringIsLong } = require("devtools/server/actors/object
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const ErrorDocs = require("devtools/server/actors/errordocs");
loader.lazyRequireGetter(this, "NetworkMonitorActor", "devtools/server/actors/network-monitor", true);
loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "NetworkEventActor", "devtools/server/actors/network-event", true);
loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
@ -73,6 +75,8 @@ function WebConsoleActor(connection, parentActor) {
this.dbg = this.parentActor.makeDebugger();
this._netEvents = new Map();
this._networkEventActorsByURL = new Map();
this._gripDepth = 0;
this._listeners = new Set();
this._lastConsoleInputEvaluation = undefined;
@ -127,6 +131,25 @@ WebConsoleActor.prototype =
*/
_prefs: null,
/**
* Holds a map between nsIChannel objects and NetworkEventActors for requests
* created with sendHTTPRequest or found via the network listener.
*
* @private
* @type Map
*/
_netEvents: null,
/**
* Holds a map from URL to NetworkEventActors for requests noticed by the network
* listener. Requests are added when they start, so the actor might not yet have all
* data for the request until it has completed.
*
* @private
* @type Map
*/
_networkEventActorsByURL: null,
/**
* Holds a set of all currently registered listeners.
*
@ -265,6 +288,16 @@ WebConsoleActor.prototype =
*/
consoleAPIListener: null,
/**
* The NetworkMonitor instance.
*/
networkMonitor: null,
/**
* The NetworkMonitor instance living in the same (child) process.
*/
networkMonitorChild: null,
/**
* The ConsoleProgressListener instance.
*/
@ -321,27 +354,18 @@ WebConsoleActor.prototype =
this.consoleServiceListener.destroy();
this.consoleServiceListener = null;
}
if (this.networkMonitorActor) {
this.networkMonitorActor.destroy();
this.networkMonitorActor = null;
}
if (this.networkMonitorActorId) {
const messageManager = this.parentActor.messageManager;
if (messageManager) {
messageManager.sendAsyncMessage("debug:destroy-network-monitor", {
actorId: this.networkMonitorActorId
});
}
this.networkMonitorActorId = null;
}
if (this.networkMonitorChildActor) {
this.networkMonitorChildActor.destroy();
this.networkMonitorChildActor = null;
}
if (this.consoleAPIListener) {
this.consoleAPIListener.destroy();
this.consoleAPIListener = null;
}
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;
@ -373,6 +397,7 @@ WebConsoleActor.prototype =
this._webConsoleCommandsCache = null;
this._lastConsoleInputEvaluation = null;
this._evalWindow = null;
this._netEvents.clear();
this.dbg.enabled = false;
this.dbg = null;
this.conn = null;
@ -550,7 +575,7 @@ WebConsoleActor.prototype =
* @return object
* The response object which holds the startedListeners array.
*/
startListeners: async function(request) {
startListeners: function(request) {
const startedListeners = [];
const window = !this.parentActor.isRootActor ? this.window : null;
let messageManager = null;
@ -598,37 +623,27 @@ WebConsoleActor.prototype =
if (isWorker) {
break;
}
if (!this.networkMonitorActorId && !this.networkMonitorActor) {
if (!this.networkMonitor) {
// Create a StackTraceCollector that's going to be shared both by
// the NetworkMonitorActor running in the same process for service worker
// requests, as well with the NetworkMonitorActor running in the parent
// process. It will communicate via message manager for this one.
this.stackTraceCollector = new StackTraceCollector({ window },
messageManager);
// the NetworkMonitorChild (getting messages about requests from
// parent) and by the NetworkMonitor that directly watches service
// workers requests.
this.stackTraceCollector = new StackTraceCollector({ window });
this.stackTraceCollector.init();
if (messageManager && processBoundary) {
// Start a network monitor in the parent process to listen to
// most requests than happen in parent
this.networkMonitorActorId = await this.conn.spawnActorInParentProcess(
this.actorID, {
module: "devtools/server/actors/network-monitor",
constructor: "NetworkMonitorActor",
args: [
{ outerWindowID: this.parentActor.outerWindowID },
this.actorID
],
});
this.networkMonitor =
new NetworkMonitorChild(this.parentActor.outerWindowID,
messageManager, this.conn, this);
this.networkMonitor.init();
// Spawn also one in the child to listen to service workers
this.networkMonitorChildActor = new NetworkMonitorActor(this.conn,
{ window },
this.actorID,
null,
this.stackTraceCollector);
this.networkMonitorChild = new NetworkMonitor({ window }, this);
this.networkMonitorChild.init();
} else {
this.networkMonitorActor = new NetworkMonitorActor(this.conn, { window },
this.actorID, null, this.stackTraceCollector);
this.networkMonitor = new NetworkMonitor({ window }, this);
this.networkMonitor.init();
}
}
startedListeners.push(listener);
@ -728,22 +743,13 @@ WebConsoleActor.prototype =
stoppedListeners.push(listener);
break;
case "NetworkActivity":
if (this.networkMonitorActor) {
this.networkMonitorActor.destroy();
this.networkMonitorActor = null;
if (this.networkMonitor) {
this.networkMonitor.destroy();
this.networkMonitor = null;
}
if (this.networkMonitorActorId) {
const messageManager = this.parentActor.messageManager;
if (messageManager) {
messageManager.sendAsyncMessage("debug:destroy-network-monitor", {
actorId: this.networkMonitorActorId
});
}
this.networkMonitorActorId = null;
}
if (this.networkMonitorChildActor) {
this.networkMonitorChildActor.destroy();
this.networkMonitorChildActor = null;
if (this.networkMonitorChild) {
this.networkMonitorChild.destroy();
this.networkMonitorChild = null;
}
if (this.stackTraceCollector) {
this.stackTraceCollector.destroy();
@ -1193,31 +1199,18 @@ WebConsoleActor.prototype =
for (const key in request.preferences) {
this._prefs[key] = request.preferences[key];
if (key == "NetworkMonitor.saveRequestAndResponseBodies") {
if (this.networkMonitorActor) {
this.networkMonitorActor.netMonitor.saveRequestAndResponseBodies =
this._prefs[key];
}
if (this.networkMonitorChildActor) {
this.networkMonitorChildActor.netMonitor.saveRequestAndResponseBodies =
this._prefs[key];
}
if (this.networkMonitorActorId) {
const messageManager = this.parentActor.messageManager;
messageManager.sendAsyncMessage("debug:netmonitor-preference",
{ saveRequestAndResponseBodies: this._prefs[key] });
}
} else if (key == "NetworkMonitor.throttleData") {
if (this.networkMonitorActor) {
this.networkMonitorActor.netMonitor.throttleData = this._prefs[key];
}
if (this.networkMonitorChildActor) {
this.networkMonitorChildActor.netMonitor.throttleData = this._prefs[key];
}
if (this.networkMonitorActorId) {
const messageManager = this.parentActor.messageManager;
messageManager.sendAsyncMessage("debug:netmonitor-preference",
{ throttleData: this._prefs[key] });
if (this.networkMonitor) {
if (key == "NetworkMonitor.saveRequestAndResponseBodies") {
this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key];
if (this.networkMonitorChild) {
this.networkMonitorChild.saveRequestAndResponseBodies =
this._prefs[key];
}
} else if (key == "NetworkMonitor.throttleData") {
this.networkMonitor.throttleData = this._prefs[key];
if (this.networkMonitorChild) {
this.networkMonitorChild.throttleData = this._prefs[key];
}
}
}
}
@ -1726,6 +1719,58 @@ WebConsoleActor.prototype =
this.conn.send(packet);
},
/**
* Handler for network events. This method is invoked when a new network event
* is about to be recorded.
*
* @see NetworkEventActor
* @see NetworkMonitor from webconsole/utils.js
*
* @param object event
* The initial network request event information.
* @return object
* A new NetworkEventActor is returned. This is used for tracking the
* network request and response.
*/
onNetworkEvent: function(event) {
const actor = this.getNetworkEventActor(event.channelId);
actor.init(event);
this._networkEventActorsByURL.set(actor._request.url, actor);
const packet = {
from: this.actorID,
type: "networkEvent",
eventActor: actor.form()
};
this.conn.send(packet);
return actor;
},
/**
* Get the NetworkEventActor for a nsIHttpChannel, if it exists,
* otherwise create a new one.
*
* @param string channelId
* The id of the channel for the network event.
* @return object
* The NetworkEventActor for the given channel.
*/
getNetworkEventActor: function(channelId) {
let actor = this._netEvents.get(channelId);
if (actor) {
// delete from map as we should only need to do this check once
this._netEvents.delete(channelId);
return actor;
}
actor = new NetworkEventActor(this);
this._actorPool.addActor(actor);
return actor;
},
/**
* Get the NetworkEventActor for a given URL that may have been noticed by the network
* listener. Requests are added when they start, so the actor might not yet have all
@ -1734,27 +1779,8 @@ WebConsoleActor.prototype =
* @param string url
* The URL of the request to search for.
*/
getRequestContentForURL(url) {
// When running in Parent Process, call the NetworkMonitorActor directly.
if (this.networkMonitorActor) {
return this.networkMonitorActor.getRequestContentForURL(url);
} else if (this.networkMonitorActorId) {
// Otherwise, if the netmonitor is started, but on the parent process,
// pipe the data through the message manager
const messageManager = this.parentActor.messageManager;
return new Promise(resolve => {
const onMessage = ({ data }) => {
if (data.url == url) {
messageManager.removeMessageListener("debug:request-content", onMessage);
resolve(data.content);
}
};
messageManager.addMessageListener("debug:request-content", onMessage);
messageManager.sendAsyncMessage("debug:request-content", { url });
});
}
// Finally, if the netmonitor is not started at all, return null
return null;
getNetworkEventActorForURL(url) {
return this._networkEventActorsByURL.get(url);
},
/**
@ -1763,8 +1789,8 @@ WebConsoleActor.prototype =
* @param object message
* Object with 'request' - the HTTP request details.
*/
async sendHTTPRequest({ request }) {
const { url, method, headers, body } = request;
sendHTTPRequest(message) {
const { url, method, headers, body } = message.request;
// Set the loadingNode and loadGroup to the target document - otherwise the
// request won't show up in the opened netmonitor.
@ -1800,30 +1826,15 @@ WebConsoleActor.prototype =
NetUtil.asyncFetch(channel, () => {});
// When running in Parent Process, call the NetworkMonitorActor directly.
const { channelId } = channel;
if (this.networkMonitorActor) {
const actor = this.networkMonitorActor.getNetworkEventActor(channelId);
return {
eventActor: actor.form()
};
} else if (this.networkMonitorActorId) {
// Otherwise, if the netmonitor is started, but on the parent process,
// pipe the data through the message manager
const messageManager = this.parentActor.messageManager;
return new Promise(resolve => {
const onMessage = ({ data }) => {
messageManager.removeMessageListener("debug:get-network-event-actor",
onMessage);
resolve({
eventActor: data
});
};
messageManager.addMessageListener("debug:get-network-event-actor", onMessage);
messageManager.sendAsyncMessage("debug:get-network-event-actor", { channelId });
});
}
return null;
const actor = this.getNetworkEventActor(channel.channelId);
// map channel to actor so we can associate future events with it
this._netEvents.set(channel.channelId, actor);
return {
from: this.actorID,
eventActor: actor.form()
};
},
/**

View File

@ -32,7 +32,6 @@ DevToolsModules(
'layout.js',
'memory.js',
'network-event.js',
'network-monitor.js',
'node.js',
'object.js',
'perf.js',

View File

@ -1,15 +0,0 @@
/* 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 } = require("devtools/shared/protocol");
const networkMonitorSpec = generateActorSpec({
typeName: "network-monitor",
methods: {},
});
exports.networkMonitorSpec = networkMonitorSpec;

View File

@ -6,7 +6,7 @@
"use strict";
const {Cc, Ci, Cm, Cr, components} = require("chrome");
const {Cc, Ci, Cm, Cu, Cr, components} = require("chrome");
const ChromeUtils = require("ChromeUtils");
const Services = require("Services");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
@ -177,37 +177,23 @@ ChannelEventSinkFactory.getService = function() {
return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink).wrappedJSObject;
};
function StackTraceCollector(filters, messageManager) {
function StackTraceCollector(filters) {
this.filters = filters;
this.stacktracesById = new Map();
this.messageManager = messageManager;
}
StackTraceCollector.prototype = {
init() {
Services.obs.addObserver(this, "http-on-opening-request");
ChannelEventSinkFactory.getService().registerCollector(this);
if (this.messageManager) {
this.onGetStack = this.onGetStack.bind(this);
this.messageManager.addMessageListener("debug:request-stack", this.onGetStack);
}
},
destroy() {
Services.obs.removeObserver(this, "http-on-opening-request");
ChannelEventSinkFactory.getService().unregisterCollector(this);
if (this.messageManager) {
this.messageManager.removeMessageListener("debug:request-stack", this.onGetStack);
}
},
_saveStackTrace(channel, stacktrace) {
if (this.messageManager) {
this.messageManager.sendAsyncMessage("debug:request-stack-available", {
channelId: channel.channelId,
stacktrace: stacktrace && stacktrace.length > 0
});
}
this.stacktracesById.set(channel.channelId, stacktrace);
},
@ -252,6 +238,7 @@ StackTraceCollector.prototype = {
const oldId = oldChannel.channelId;
const stacktrace = this.stacktracesById.get(oldId);
if (stacktrace) {
this.stacktracesById.delete(oldId);
this._saveStackTrace(newChannel, stacktrace);
}
},
@ -260,16 +247,7 @@ StackTraceCollector.prototype = {
const trace = this.stacktracesById.get(channelId);
this.stacktracesById.delete(channelId);
return trace;
},
onGetStack(msg) {
const channelId = msg.data;
const stack = this.getStackTrace(channelId);
this.messageManager.sendAsyncMessage("debug:request-stack", {
channelId,
stack,
});
},
}
};
exports.StackTraceCollector = StackTraceCollector;
@ -763,6 +741,9 @@ NetworkResponseListener.prototype = {
* given the initial network request information as an argument.
* 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) {
this.filters = filters;
@ -1155,6 +1136,12 @@ NetworkMonitor.prototype = {
}
}
// 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: causeTypeToString(causeType),
loadingDocumentUri: causeUri,
@ -1736,6 +1723,328 @@ NetworkMonitor.prototype = {
},
};
/**
* 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.
*
* Network requests for apps happen in the main process. As such,
* a NetworkMonitor instance is used by the WebappsActor in the main process to
* log the network requests for this child process.
*
* The main process creates NetworkEventActorProxy instances per request. These
* send the data to this object using the nsIMessageManager. Here we proxy the
* data to the WebConsoleActor or to a NetworkEventActor.
*
* @constructor
* @param number outerWindowID
* The outerWindowID of the target actor's main window.
* @param nsIMessageManager messageManager
* The nsIMessageManager to use to communicate with the parent process.
* @param object DebuggerServerConnection
* The RDP connection to the client.
* @param object owner
* The WebConsoleActor that is listening for the network requests.
*/
function NetworkMonitorChild(outerWindowID, messageManager, conn, owner) {
this.outerWindowID = outerWindowID;
this.conn = conn;
this.owner = owner;
this._messageManager = messageManager;
this._onNewEvent = this._onNewEvent.bind(this);
this._onUpdateEvent = this._onUpdateEvent.bind(this);
this._netEvents = new Map();
this._msgName = `debug:${this.conn.prefix}netmonitor`;
}
exports.NetworkMonitorChild = NetworkMonitorChild;
NetworkMonitorChild.prototype = {
owner: null,
_netEvents: null,
_saveRequestAndResponseBodies: true,
_throttleData: null,
get saveRequestAndResponseBodies() {
return this._saveRequestAndResponseBodies;
},
set saveRequestAndResponseBodies(val) {
this._saveRequestAndResponseBodies = val;
this._messageManager.sendAsyncMessage(this._msgName, {
action: "setPreferences",
preferences: {
saveRequestAndResponseBodies: this._saveRequestAndResponseBodies,
},
});
},
get throttleData() {
return this._throttleData;
},
set throttleData(val) {
this._throttleData = val;
this._messageManager.sendAsyncMessage(this._msgName, {
action: "setPreferences",
preferences: {
throttleData: this._throttleData,
},
});
},
init: function() {
this.conn.setupInParent({
module: "devtools/shared/webconsole/network-monitor",
setupParent: "setupParentProcess"
});
const mm = this._messageManager;
mm.addMessageListener(`${this._msgName}:newEvent`, this._onNewEvent);
mm.addMessageListener(`${this._msgName}:updateEvent`, this._onUpdateEvent);
mm.sendAsyncMessage(this._msgName, {
outerWindowID: this.outerWindowID,
action: "start",
});
},
_onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) {
const {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);
}
const actor = this.owner.onNetworkEvent(event);
this._netEvents.set(id, Cu.getWeakReference(actor));
}),
_onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) {
const {id, method, args} = msg.data;
const weakActor = this._netEvents.get(id);
const actor = weakActor ? weakActor.get() : null;
if (!actor) {
console.error(`Received ${this._msgName}:updateEvent for unknown event ID: ${id}`);
return;
}
if (!(method in actor)) {
console.error(`Received ${this._msgName}:updateEvent unsupported ` +
`method: ${method}`);
return;
}
actor[method].apply(actor, args);
}),
destroy: function() {
const mm = this._messageManager;
try {
mm.removeMessageListener(`${this._msgName}:newEvent`, this._onNewEvent);
mm.removeMessageListener(`${this._msgName}:updateEvent`, this._onUpdateEvent);
} catch (e) {
// On b2g, when registered to a new root docshell,
// all message manager functions throw when trying to call them during
// message-manager-disconnect event.
// As there is no attribute/method on message manager to know
// if they are still usable or not, we can only catch the exception...
}
this._netEvents.clear();
this._messageManager = null;
this.conn = null;
this.owner = null;
},
};
/**
* The NetworkEventActorProxy is used to send network request information from
* the main process to the child app process. One proxy is used per request.
* Similarly, one NetworkEventActor in the child app process is used per
* request. The client receives all network logs from the child actors.
*
* The child process has a NetworkMonitorChild instance that is listening for
* all network logging from the main process. The net monitor shim is used to
* proxy the data to the WebConsoleActor instance of the child process.
*
* @constructor
* @param nsIMessageManager messageManager
* The message manager for the child app process. This is used for
* communication with the NetworkMonitorChild instance of the process.
* @param string msgName
* The message name to be used for this connection.
*/
function NetworkEventActorProxy(messageManager, msgName) {
this.id = gSequenceId();
this.messageManager = messageManager;
this._msgName = msgName;
}
exports.NetworkEventActorProxy = NetworkEventActorProxy;
NetworkEventActorProxy.methodFactory = function(method) {
return DevToolsUtils.makeInfallible(function() {
const args = Array.slice(arguments);
const mm = this.messageManager;
mm.sendAsyncMessage(`${this._msgName}:updateEvent`, {
id: this.id,
method: method,
args: args,
});
}, "NetworkEventActorProxy." + method);
};
NetworkEventActorProxy.prototype = {
/**
* Initialize the network event. This method sends the network request event
* to the content process.
*
* @param object event
* Object describing the network request.
* @return object
* This object.
*/
init: DevToolsUtils.makeInfallible(function(event) {
const mm = this.messageManager;
mm.sendAsyncMessage(`${this._msgName}:newEvent`, {
id: this.id,
event: event,
});
return this;
}),
};
(function() {
// Listeners for new network event data coming from the NetworkMonitor.
const methods = ["addRequestHeaders", "addRequestCookies", "addRequestPostData",
"addResponseStart", "addSecurityInfo", "addResponseHeaders",
"addResponseCookies", "addResponseContent", "addResponseCache",
"addEventTimings"];
const factory = NetworkEventActorProxy.methodFactory;
for (const method of methods) {
NetworkEventActorProxy.prototype[method] = factory(method);
}
})();
/**
* This is triggered by the child calling `setupInParent` when the child's network monitor
* is starting up. This initializes the parent process side of the monitoring.
*/
function setupParentProcess({ mm, prefix }) {
let networkMonitor = new NetworkMonitorParent(mm, prefix);
return {
onBrowserSwap: newMM => networkMonitor.setMessageManager(newMM),
onDisconnected: () => {
networkMonitor.destroy();
networkMonitor = null;
}
};
}
exports.setupParentProcess = setupParentProcess;
/**
* The NetworkMonitorParent runs in the parent process and uses the message manager to
* listen for requests from the child process to start/stop the network monitor. Most
* request data is only available from the parent process, so that's why the network
* monitor needs to run there when debugging tabs that are in the child.
*
* @param nsIMessageManager mm
* The message manager for the browser we're filtering on.
* @param string prefix
* The RDP connection prefix that uniquely identifies the connection.
*/
function NetworkMonitorParent(mm, prefix) {
this._msgName = `debug:${prefix}netmonitor`;
this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this);
this.onNetworkEvent = this.onNetworkEvent.bind(this);
this.setMessageManager(mm);
}
NetworkMonitorParent.prototype = {
netMonitor: null,
messageManager: null,
setMessageManager(mm) {
if (this.messageManager) {
const oldMM = this.messageManager;
oldMM.removeMessageListener(this._msgName, this.onNetMonitorMessage);
}
this.messageManager = mm;
if (mm) {
mm.addMessageListener(this._msgName, this.onNetMonitorMessage);
}
},
/**
* Handler for `debug:${prefix}netmonitor` messages received through the message manager
* from the content process.
*
* @param object msg
* Message from the content.
*/
onNetMonitorMessage: DevToolsUtils.makeInfallible(function(msg) {
const {action} = msg.json;
// Pipe network monitor data from parent to child via the message manager.
switch (action) {
case "start":
if (!this.netMonitor) {
const {appId, outerWindowID} = msg.json;
this.netMonitor = new NetworkMonitor({
outerWindowID,
appId,
}, this);
this.netMonitor.init();
}
break;
case "setPreferences": {
const {preferences} = msg.json;
for (const key of Object.keys(preferences)) {
if ((key == "saveRequestAndResponseBodies" ||
key == "throttleData") && this.netMonitor) {
this.netMonitor[key] = preferences[key];
}
}
break;
}
case "stop":
if (this.netMonitor) {
this.netMonitor.destroy();
this.netMonitor = null;
}
break;
case "disconnect":
this.destroy();
break;
}
}),
/**
* Handler for new network requests. This method is invoked by the current
* NetworkMonitor instance.
*
* @param object event
* Object describing the network request.
* @return object
* A NetworkEventActorProxy instance which is notified when further
* data about the request is available.
*/
onNetworkEvent: DevToolsUtils.makeInfallible(function(event) {
return new NetworkEventActorProxy(this.messageManager, this._msgName).init(event);
}),
destroy: function() {
this.setMessageManager(null);
if (this.netMonitor) {
this.netMonitor.destroy();
this.netMonitor = null;
}
},
};
/**
* A WebProgressListener that listens for location changes.
*