Bug 1479524 - Always use message manager with NetworkMonitorActor. r=jdescottes

MozReview-Commit-ID: AXOd0i4NOjH
This commit is contained in:
Alexandre Poirot 2018-07-27 02:05:14 -07:00
Родитель edb2dd1da5
Коммит b840eb27bf
5 изменённых файлов: 231 добавлений и 173 удалений

Просмотреть файл

@ -28,43 +28,43 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
* 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.
* @param nsIMessageManager messageManager
* This is the manager to use to communicate with the console actor. When both
* netmonitor and console actor runs in the same process, this is an instance
* of MockMessageManager instead of a real message manager.
*/
initialize(conn, filters, parentID, messageManager, stackTraceCollector) {
initialize(conn, filters, parentID, messageManager) {
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);
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.onDestroyMessage = this.onDestroyMessage.bind(this);
this.messageManager.addMessageListener("debug:destroy-network-monitor",
this.onDestroyMessage);
},
onDestroyMessage({ data }) {
if (data.actorID == this.parentID) {
this.destroy();
}
},
@ -76,8 +76,8 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
this.netMonitor = null;
}
this.stackTraces.clear();
if (this.messageManager) {
this.stackTraces.clear();
this.messageManager.removeMessageListener("debug:request-stack-available",
this.onStackTraceAvailable);
this.messageManager.removeMessageListener("debug:request-content",
@ -87,7 +87,7 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
this.messageManager.removeMessageListener("debug:get-network-event-actor",
this.onGetNetworkEventActor);
this.messageManager.removeMessageListener("debug:destroy-network-monitor",
this.destroy);
this.onDestroyMessage);
this.messageManager = null;
}
},
@ -101,11 +101,7 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
}
},
getRequestContentForURL(url) {
const actor = this._networkEventActorsByURL.get(url);
if (!actor) {
return null;
}
getRequestContentForActor(actor) {
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
@ -133,7 +129,10 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
onRequestContent(msg) {
const { url } = msg.data;
const content = this.getRequestContentForURL(url);
const actor = this._networkEventActorsByURL.get(url);
// Always reply with a message, but with a null `content` if this instance
// did not processed this request
const content = actor ? this.getRequestContentForActor(actor) : null;
this.messageManager.sendAsyncMessage("debug:request-content", {
url,
content,
@ -151,7 +150,10 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
onGetNetworkEventActor({ data }) {
const actor = this.getNetworkEventActor(data.channelId);
this.messageManager.sendAsyncMessage("debug:get-network-event-actor", actor.form());
this.messageManager.sendAsyncMessage("debug:get-network-event-actor", {
channelId: data.channelId,
actor: actor.form()
});
},
getNetworkEventActor(channelId) {
@ -175,13 +177,9 @@ const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
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);
event.cause.stacktrace = this.stackTraces.has(channelId);
if (event.cause.stacktrace) {
this.stackTraces.delete(channelId);
}
actor.init(event);

Просмотреть файл

@ -30,6 +30,7 @@ loader.lazyRequireGetter(this, "addWebConsoleCommands", "devtools/server/actors/
loader.lazyRequireGetter(this, "formatCommand", "devtools/server/actors/webconsole/commands", true);
loader.lazyRequireGetter(this, "isCommand", "devtools/server/actors/webconsole/commands", true);
loader.lazyRequireGetter(this, "validCommands", "devtools/server/actors/webconsole/commands", true);
loader.lazyRequireGetter(this, "createMessageManagerMocks", "devtools/server/actors/webconsole/message-manager-mock", true);
loader.lazyRequireGetter(this, "CONSOLE_WORKER_IDS", "devtools/server/actors/webconsole/utils", true);
loader.lazyRequireGetter(this, "WebConsoleUtils", "devtools/server/actors/webconsole/utils", true);
loader.lazyRequireGetter(this, "EnvironmentActor", "devtools/server/actors/environment", true);
@ -325,22 +326,13 @@ 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) {
if (this.netmonitors) {
for (const { messageManager } of this.netmonitors) {
messageManager.sendAsyncMessage("debug:destroy-network-monitor", {
actorId: this.networkMonitorActorId
actorID: this.actorID
});
}
this.networkMonitorActorId = null;
}
if (this.networkMonitorChildActor) {
this.networkMonitorChildActor.destroy();
this.networkMonitorChildActor = null;
this.netmonitors = null;
}
if (this.consoleAPIListener) {
this.consoleAPIListener.destroy();
@ -589,20 +581,6 @@ WebConsoleActor.prototype =
startListeners: async function(request) {
const startedListeners = [];
const window = !this.parentActor.isRootActor ? this.window : null;
let messageManager = null;
// Check if the actor is running in a child process (but only if
// Services.appinfo exists, to prevent startListeners to fail
// when the target is a Worker).
const processBoundary = Services.appinfo && (
Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
);
// Retrieve a message manager from the parent actor if this actor is
// not currently running in the main process.
if (processBoundary) {
messageManager = this.parentActor.messageManager;
}
while (request.listeners.length > 0) {
const listener = request.listeners.shift();
@ -634,19 +612,33 @@ WebConsoleActor.prototype =
if (isWorker) {
break;
}
if (!this.networkMonitorActorId && !this.networkMonitorActor) {
// 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);
this.stackTraceCollector.init();
if (!this.netmonitors) {
// Instanciate fake message managers used for service worker's netmonitor
// when running in the content process, and for netmonitor running in the
// same process when running in the parent process.
// `createMessageManagerMocks` returns a couple of connected messages
// managers that pass messages to each other to simulate the process
// boundary. We will use the first one for the webconsole-actor and the
// second one will be used by the netmonitor-actor.
const [ mmMockParent, mmMockChild ] = createMessageManagerMocks();
if (messageManager && processBoundary) {
// Maintain the list of message manager we should message to/listen from
// to support the netmonitor instances, also records actorID of each
// NetworkMonitorActor.
// Array of `{ messageManager, parentProcess }`.
// Where `parentProcess` is true for the netmonitor actor instanciated in the
// parent process.
this.netmonitors = [];
// Check if the actor is running in a content process
const isInContentProcess =
Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT &&
this.parentActor.messageManager;
if (isInContentProcess) {
// Start a network monitor in the parent process to listen to
// most requests than happen in parent
this.networkMonitorActorId = await this.conn.spawnActorInParentProcess(
// most requests that happen in parent. This one will communicate through
// `messageManager`.
await this.conn.spawnActorInParentProcess(
this.actorID, {
module: "devtools/server/actors/network-monitor",
constructor: "NetworkMonitorActor",
@ -655,17 +647,34 @@ WebConsoleActor.prototype =
this.actorID
],
});
// Spawn also one in the child to listen to service workers
this.networkMonitorChildActor = new NetworkMonitorActor(this.conn,
{ window },
this.actorID,
null,
this.stackTraceCollector);
} else {
this.networkMonitorActor = new NetworkMonitorActor(this.conn, { window },
this.actorID, null, this.stackTraceCollector);
this.netmonitors.push({
messageManager: this.parentActor.messageManager,
parentProcess: true
});
}
// When the console actor runs in the parent process, Netmonitor can be ran
// in the process and communicate through `messageManagerMock`.
// And while it runs in the content process, we also spawn one in the content
// to listen to requests that happen in the content process (for instance
// service workers requests)
new NetworkMonitorActor(this.conn,
{ window },
this.actorID,
mmMockParent);
this.netmonitors.push({
messageManager: mmMockChild,
parentProcess: !isInContentProcess
});
// 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 },
this.netmonitors);
this.stackTraceCollector.init();
}
startedListeners.push(listener);
break;
@ -764,22 +773,13 @@ WebConsoleActor.prototype =
stoppedListeners.push(listener);
break;
case "NetworkActivity":
if (this.networkMonitorActor) {
this.networkMonitorActor.destroy();
this.networkMonitorActor = null;
}
if (this.networkMonitorActorId) {
const messageManager = this.parentActor.messageManager;
if (messageManager) {
if (this.netmonitors) {
for (const { messageManager } of this.netmonitors) {
messageManager.sendAsyncMessage("debug:destroy-network-monitor", {
actorId: this.networkMonitorActorId
actorID: this.actorID
});
}
this.networkMonitorActorId = null;
}
if (this.networkMonitorChildActor) {
this.networkMonitorChildActor.destroy();
this.networkMonitorChildActor = null;
this.netmonitors = null;
}
if (this.stackTraceCollector) {
this.stackTraceCollector.destroy();
@ -1252,31 +1252,19 @@ 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.netmonitors) {
if (key == "NetworkMonitor.saveRequestAndResponseBodies") {
for (const { messageManager } of this.netmonitors) {
messageManager.sendAsyncMessage("debug:netmonitor-preference", {
saveRequestAndResponseBodies: this._prefs[key]
});
}
} else if (key == "NetworkMonitor.throttleData") {
for (const { messageManager } of this.netmonitors) {
messageManager.sendAsyncMessage("debug:netmonitor-preference", {
throttleData: this._prefs[key]
});
}
}
}
}
@ -1818,26 +1806,30 @@ WebConsoleActor.prototype =
* 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) {
if (!this.netmonitors) {
return null;
}
return new Promise(resolve => {
let messagesReceived = 0;
const onMessage = ({ data }) => {
if (data.url != url) {
return;
}
messagesReceived++;
// Either use the first response with a content, or return a null content
// if we received the responses from all the message managers.
if (data.content || messagesReceived == this.netmonitors.length) {
for (const { messageManager } of this.netmonitors) {
messageManager.removeMessageListener("debug:request-content", onMessage);
resolve(data.content);
}
};
resolve(data.content);
}
};
for (const { messageManager } of this.netmonitors) {
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;
}
});
},
/**
@ -1883,30 +1875,28 @@ WebConsoleActor.prototype =
NetUtil.asyncFetch(channel, () => {});
// When running in Parent Process, call the NetworkMonitorActor directly.
if (!this.netmonitors) {
return null;
}
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 }) => {
// Only query the NetworkMonitorActor running in the parent process, where the
// request will be done. There always is one listener running in the parent process,
// see startListeners.
const netmonitor = this.netmonitors.filter(({ parentProcess }) => parentProcess)[0];
const { messageManager } = netmonitor;
return new Promise(resolve => {
const onMessage = ({ data }) => {
if (data.channelId == channelId) {
messageManager.removeMessageListener("debug:get-network-event-actor",
onMessage);
resolve({
eventActor: data
eventActor: data.actor
});
};
messageManager.addMessageListener("debug:get-network-event-actor", onMessage);
messageManager.sendAsyncMessage("debug:get-network-event-actor", { channelId });
});
}
return null;
}
};
messageManager.addMessageListener("debug:get-network-event-actor", onMessage);
messageManager.sendAsyncMessage("debug:get-network-event-actor", { channelId });
});
},
/**

Просмотреть файл

@ -0,0 +1,68 @@
/* 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";
/**
* Implements a fake MessageManager class that allows to use the message
* manager API within the same process. This implementation will forward
* messages within the same process.
*
* It helps having the same codepath for actors being evaluated in the same
* process *and* in a remote one.
*/
function MessageManagerMock() {
this._listeners = new Map();
}
MessageManagerMock.prototype = {
addMessageListener(name, listener) {
let listeners = this._listeners.get(name);
if (!listeners) {
listeners = [];
this._listeners.set(name, listeners);
}
if (!listeners.includes(listener)) {
listeners.push(listener);
}
},
removeMessageListener(name, listener) {
const listeners = this._listeners.get(name);
const idx = listeners.indexOf(listener);
listeners.splice(idx, 1);
},
sendAsyncMessage(name, data) {
this.other.internalSendAsyncMessage(name, data);
},
internalSendAsyncMessage(name, data) {
const listeners = this._listeners.get(name);
if (!listeners) {
return;
}
const message = {
target: this,
data
};
for (const listener of listeners) {
if (typeof listener === "object" &&
typeof listener.receiveMessage === "function") {
listener.receiveMessage(message);
} else if (typeof listener === "function") {
listener(message);
}
}
},
};
/**
* Create two MessageManager mocks, connected to each others.
* Calling sendAsyncMessage on the first will dispatch messages on the second one,
* and the other way around
*/
exports.createMessageManagerMocks = function() {
const a = new MessageManagerMock();
const b = new MessageManagerMock();
a.other = b;
b.other = a;
return [a, b];
};

Просмотреть файл

@ -8,6 +8,7 @@ DevToolsModules(
'commands.js',
'content-process-forward.js',
'listeners.js',
'message-manager-mock.js',
'screenshot.js',
'utils.js',
'worker-listeners.js',

Просмотреть файл

@ -177,33 +177,33 @@ ChannelEventSinkFactory.getService = function() {
return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink).wrappedJSObject;
};
function StackTraceCollector(filters, messageManager) {
function StackTraceCollector(filters, netmonitors) {
this.filters = filters;
this.stacktracesById = new Map();
this.messageManager = messageManager;
this.netmonitors = netmonitors;
}
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);
this.onGetStack = this.onGetStack.bind(this);
for (const { messageManager } of this.netmonitors) {
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);
for (const { messageManager } of this.netmonitors) {
messageManager.removeMessageListener("debug:request-stack", this.onGetStack);
}
},
_saveStackTrace(channel, stacktrace) {
if (this.messageManager) {
this.messageManager.sendAsyncMessage("debug:request-stack-available", {
for (const { messageManager } of this.netmonitors) {
messageManager.sendAsyncMessage("debug:request-stack-available", {
channelId: channel.channelId,
stacktrace: stacktrace && stacktrace.length > 0
});
@ -263,9 +263,10 @@ StackTraceCollector.prototype = {
},
onGetStack(msg) {
const messageManager = msg.target;
const channelId = msg.data;
const stack = this.getStackTrace(channelId);
this.messageManager.sendAsyncMessage("debug:request-stack", {
messageManager.sendAsyncMessage("debug:request-stack", {
channelId,
stack,
});