From 9aee8eedf442ab7f6338cc8c4e2ad127a4bd902c Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Tue, 1 Aug 2017 16:51:07 +0200 Subject: [PATCH] Bug 1382968 - Only try to forward console messages from content processes to parent when browser console/toolbox are opened. r=jryans,tromey MozReview-Commit-ID: 8cFUbF4msHx --HG-- rename : toolkit/components/processsingleton/ContentProcessSingleton.js => devtools/server/actors/webconsole/content-process-forward.js extra : rebase_source : 8f30b1484e8ec1410f59a8764dced0e645ebec28 --- .../webconsole/webconsole-connection-proxy.js | 5 + devtools/server/actors/webconsole.js | 24 +++- .../webconsole/content-process-forward.js | 136 ++++++++++++++++++ .../server/actors/webconsole/listeners.js | 39 +++++ devtools/server/actors/webconsole/moz.build | 1 + .../ContentProcessSingleton.js | 90 ------------ .../processsingleton/MainProcessSingleton.js | 8 -- 7 files changed, 204 insertions(+), 99 deletions(-) create mode 100644 devtools/server/actors/webconsole/content-process-forward.js diff --git a/devtools/client/webconsole/webconsole-connection-proxy.js b/devtools/client/webconsole/webconsole-connection-proxy.js index 44b52f95e82f..3bb8fdd94099 100644 --- a/devtools/client/webconsole/webconsole-connection-proxy.js +++ b/devtools/client/webconsole/webconsole-connection-proxy.js @@ -178,6 +178,11 @@ WebConsoleConnectionProxy.prototype = { _attachConsole: function () { let listeners = ["PageError", "ConsoleAPI", "NetworkActivity", "FileActivity"]; + // Enable the forwarding of console messages to the parent process + // when we open the Browser Console or Toolbox. + if (this.target.chrome && !this.target.isAddon) { + listeners.push("ContentProcessMessages"); + } this.client.attachConsole(this._consoleActor, listeners, this._onAttachConsole); }, diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js index ec929e2555b4..c91811108755 100644 --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -39,6 +39,7 @@ if (isWorker) { loader.lazyRequireGetter(this, "ConsoleAPIListener", "devtools/server/actors/webconsole/listeners", true); loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/webconsole/listeners", true); loader.lazyRequireGetter(this, "ConsoleReflowListener", "devtools/server/actors/webconsole/listeners", true); + loader.lazyRequireGetter(this, "ContentProcessListener", "devtools/server/actors/webconsole/listeners", true); } /** @@ -368,6 +369,10 @@ WebConsoleActor.prototype = this.serverLoggingListener.destroy(); this.serverLoggingListener = null; } + if (this.contentProcessListener) { + this.contentProcessListener.destroy(); + this.contentProcessListener = null; + } events.off(this.parentActor, "changed-toplevel-document", this._onChangedToplevelDocument); @@ -664,6 +669,16 @@ WebConsoleActor.prototype = } startedListeners.push(listener); break; + case "ContentProcessMessages": + // Workers don't support this message type + if (isWorker) { + break; + } + if (!this.contentProcessListener) { + this.contentProcessListener = new ContentProcessListener(this); + } + startedListeners.push(listener); + break; } } @@ -693,7 +708,7 @@ WebConsoleActor.prototype = // listeners. let toDetach = request.listeners || ["PageError", "ConsoleAPI", "NetworkActivity", - "FileActivity", "ServerLogging"]; + "FileActivity", "ServerLogging", "ContentProcessMessages"]; while (toDetach.length > 0) { let listener = toDetach.shift(); @@ -749,6 +764,13 @@ WebConsoleActor.prototype = } stoppedListeners.push(listener); break; + case "ContentProcessMessages": + if (this.contentProcessListener) { + this.contentProcessListener.destroy(); + this.contentProcessListener = null; + } + stoppedListeners.push(listener); + break; } } diff --git a/devtools/server/actors/webconsole/content-process-forward.js b/devtools/server/actors/webconsole/content-process-forward.js new file mode 100644 index 000000000000..91bcce553dd9 --- /dev/null +++ b/devtools/server/actors/webconsole/content-process-forward.js @@ -0,0 +1,136 @@ +/* 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 Cu = Components.utils; +const Ci = Components.interfaces; + +const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); +const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); + +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", + "@mozilla.org/childprocessmessagemanager;1", + "nsIMessageSender"); +XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils", + "resource:///modules/E10SUtils.jsm"); + +/* + * The message manager has an upper limit on message sizes that it can + * reliably forward to the parent so we limit the size of console log event + * messages that we forward here. The web console is local and receives the + * full console message, but addons subscribed to console event messages + * in the parent receive the truncated version. Due to fragmentation, + * messages as small as 1MB have resulted in IPC allocation failures on + * 32-bit platforms. To limit IPC allocation sizes, console.log messages + * with arguments with total size > MSG_MGR_CONSOLE_MAX_SIZE (bytes) have + * their arguments completely truncated. MSG_MGR_CONSOLE_VAR_SIZE is an + * approximation of how much space (in bytes) a JS non-string variable will + * require in the manager's implementation. For strings, we use 2 bytes per + * char. The console message URI and function name are limited to + * MSG_MGR_CONSOLE_INFO_MAX characters. We don't attempt to calculate + * the exact amount of space the message manager implementation will require + * for a given message so this is imperfect. + */ +const MSG_MGR_CONSOLE_MAX_SIZE = 1024 * 1024; // 1MB +const MSG_MGR_CONSOLE_VAR_SIZE = 8; +const MSG_MGR_CONSOLE_INFO_MAX = 1024; + +function ContentProcessForward() { + Services.obs.addObserver(this, "console-api-log-event"); + Services.obs.addObserver(this, "xpcom-shutdown"); + cpmm.addMessageListener("DevTools:StopForwardingContentProcessMessage", this); +} +ContentProcessForward.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), + + receiveMessage(message) { + if (message.name == "DevTools:StopForwardingContentProcessMessage") { + this.uninit(); + } + }, + + observe(subject, topic, data) { + switch (topic) { + case "console-api-log-event": { + let consoleMsg = subject.wrappedJSObject; + + let msgData = { + level: consoleMsg.level, + filename: consoleMsg.filename.substring(0, MSG_MGR_CONSOLE_INFO_MAX), + lineNumber: consoleMsg.lineNumber, + functionName: consoleMsg.functionName && + consoleMsg.functionName.substring(0, MSG_MGR_CONSOLE_INFO_MAX), + timeStamp: consoleMsg.timeStamp, + addonId: consoleMsg.addonId, + arguments: [], + }; + + // We can't send objects over the message manager, so we sanitize + // them out, replacing those arguments with "". + let unavailString = ""; + let unavailStringLength = unavailString.length * 2; // 2-bytes per char + + // When the sum of argument sizes reaches MSG_MGR_CONSOLE_MAX_SIZE, + // replace all arguments with "". + let totalArgLength = 0; + + // Walk through the arguments, checking the type and size. + for (let arg of consoleMsg.arguments) { + if ((typeof arg == "object" || typeof arg == "function") && + arg !== null) { + if (Services.appinfo.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE) { + // For OOP extensions: we want the developer to be able to see the + // logs in the Browser Console. When the Addon Toolbox will be more + // prominent we can revisit. + try { + // If the argument is clonable, then send it as-is. If + // cloning fails, fall back to the unavailable string. + arg = Cu.cloneInto(arg, {}); + } catch (e) { + arg = unavailString; + } + } else { + arg = unavailString; + } + totalArgLength += unavailStringLength; + } else if (typeof arg == "string") { + totalArgLength += arg.length * 2; // 2-bytes per char + } else { + totalArgLength += MSG_MGR_CONSOLE_VAR_SIZE; + } + + if (totalArgLength <= MSG_MGR_CONSOLE_MAX_SIZE) { + msgData.arguments.push(arg); + } else { + // arguments take up too much space + msgData.arguments = [""]; + break; + } + } + + cpmm.sendAsyncMessage("Console:Log", msgData); + break; + } + + case "xpcom-shutdown": + this.uninit(); + break; + } + }, + + uninit() { + Services.obs.removeObserver(this, "console-api-log-event"); + Services.obs.removeObserver(this, "xpcom-shutdown"); + cpmm.removeMessageListener("DevTools:StopForwardingContentProcessMessage", this); + } +}; + +// loadProcessScript loads in all processes, including the parent, +// in which we don't need any forwarding +if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { + new ContentProcessForward(); +} + diff --git a/devtools/server/actors/webconsole/listeners.js b/devtools/server/actors/webconsole/listeners.js index 94d8d051b5c9..ec4b3dfcd4a6 100644 --- a/devtools/server/actors/webconsole/listeners.js +++ b/devtools/server/actors/webconsole/listeners.js @@ -15,6 +15,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "@mozilla.org/serviceworkers/manager;1", "nsIServiceWorkerManager"); +// Process script used to forward console calls from content processes to parent process +const CONTENT_PROCESS_SCRIPT = "resource://devtools/server/actors/webconsole/content-process-forward.js"; + // The page errors listener /** @@ -451,3 +454,39 @@ ConsoleReflowListener.prototype = this.listener = this.docshell = null; }, }; + +/** + * Forward console message calls from content processes to the parent process. + * Used by Browser console and toolbox to see messages from all processes. + * + * @constructor + * @param object owner + * The listener owner which needs to implement: + * - onConsoleAPICall(message) + */ +function ContentProcessListener(listener) { + this.listener = listener; + + Services.ppmm.addMessageListener("Console:Log", this); + Services.ppmm.loadProcessScript(CONTENT_PROCESS_SCRIPT, true); +} + +exports.ContentProcessListener = ContentProcessListener; + +ContentProcessListener.prototype = { + receiveMessage(message) { + let logMsg = message.data; + logMsg.wrappedJSObject = logMsg; + this.listener.onConsoleAPICall(logMsg); + }, + + destroy() { + // Tell the content processes to stop listening and forwarding messages + Services.ppmm.broadcastAsyncMessage("DevTools:StopForwardingContentProcessMessage"); + + Services.ppmm.removeMessageListener("Console:Log", this); + Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT); + + this.listener = null; + } +}; diff --git a/devtools/server/actors/webconsole/moz.build b/devtools/server/actors/webconsole/moz.build index d6e64676952c..c26539f4a301 100644 --- a/devtools/server/actors/webconsole/moz.build +++ b/devtools/server/actors/webconsole/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'content-process-forward.js', 'listeners.js', 'utils.js', 'worker-listeners.js', diff --git a/toolkit/components/processsingleton/ContentProcessSingleton.js b/toolkit/components/processsingleton/ContentProcessSingleton.js index 96701732c052..284e09e73ec5 100644 --- a/toolkit/components/processsingleton/ContentProcessSingleton.js +++ b/toolkit/components/processsingleton/ContentProcessSingleton.js @@ -10,35 +10,8 @@ const Ci = Components.interfaces; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "cpmm", - "@mozilla.org/childprocessmessagemanager;1", - "nsIMessageSender"); - XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController", "resource://gre/modules/TelemetryController.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils", - "resource:///modules/E10SUtils.jsm"); - -/* - * The message manager has an upper limit on message sizes that it can - * reliably forward to the parent so we limit the size of console log event - * messages that we forward here. The web console is local and receives the - * full console message, but addons subscribed to console event messages - * in the parent receive the truncated version. Due to fragmentation, - * messages as small as 1MB have resulted in IPC allocation failures on - * 32-bit platforms. To limit IPC allocation sizes, console.log messages - * with arguments with total size > MSG_MGR_CONSOLE_MAX_SIZE (bytes) have - * their arguments completely truncated. MSG_MGR_CONSOLE_VAR_SIZE is an - * approximation of how much space (in bytes) a JS non-string variable will - * require in the manager's implementation. For strings, we use 2 bytes per - * char. The console message URI and function name are limited to - * MSG_MGR_CONSOLE_INFO_MAX characters. We don't attempt to calculate - * the exact amount of space the message manager implementation will require - * for a given message so this is imperfect. - */ -const MSG_MGR_CONSOLE_MAX_SIZE = 1024 * 1024; // 1MB -const MSG_MGR_CONSOLE_VAR_SIZE = 8; -const MSG_MGR_CONSOLE_INFO_MAX = 1024; function ContentProcessSingleton() {} ContentProcessSingleton.prototype = { @@ -49,74 +22,11 @@ ContentProcessSingleton.prototype = { observe(subject, topic, data) { switch (topic) { case "app-startup": { - Services.obs.addObserver(this, "console-api-log-event"); Services.obs.addObserver(this, "xpcom-shutdown"); TelemetryController.observe(null, topic, null); break; } - case "console-api-log-event": { - let consoleMsg = subject.wrappedJSObject; - - let msgData = { - level: consoleMsg.level, - filename: consoleMsg.filename.substring(0, MSG_MGR_CONSOLE_INFO_MAX), - lineNumber: consoleMsg.lineNumber, - functionName: consoleMsg.functionName && - consoleMsg.functionName.substring(0, MSG_MGR_CONSOLE_INFO_MAX), - timeStamp: consoleMsg.timeStamp, - addonId: consoleMsg.addonId, - arguments: [], - }; - - // We can't send objects over the message manager, so we sanitize - // them out, replacing those arguments with "". - let unavailString = ""; - let unavailStringLength = unavailString.length * 2; // 2-bytes per char - - // When the sum of argument sizes reaches MSG_MGR_CONSOLE_MAX_SIZE, - // replace all arguments with "". - let totalArgLength = 0; - - // Walk through the arguments, checking the type and size. - for (let arg of consoleMsg.arguments) { - if ((typeof arg == "object" || typeof arg == "function") && - arg !== null) { - if (Services.appinfo.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE) { - // For OOP extensions: we want the developer to be able to see the - // logs in the Browser Console. When the Addon Toolbox will be more - // prominent we can revisit. - try { - // If the argument is clonable, then send it as-is. If - // cloning fails, fall back to the unavailable string. - arg = Cu.cloneInto(arg, {}); - } catch (e) { - arg = unavailString; - } - } else { - arg = unavailString; - } - totalArgLength += unavailStringLength; - } else if (typeof arg == "string") { - totalArgLength += arg.length * 2; // 2-bytes per char - } else { - totalArgLength += MSG_MGR_CONSOLE_VAR_SIZE; - } - - if (totalArgLength <= MSG_MGR_CONSOLE_MAX_SIZE) { - msgData.arguments.push(arg); - } else { - // arguments take up too much space - msgData.arguments = [""]; - break; - } - } - - cpmm.sendAsyncMessage("Console:Log", msgData); - break; - } - case "xpcom-shutdown": - Services.obs.removeObserver(this, "console-api-log-event"); Services.obs.removeObserver(this, "xpcom-shutdown"); break; } diff --git a/toolkit/components/processsingleton/MainProcessSingleton.js b/toolkit/components/processsingleton/MainProcessSingleton.js index 681670fb3428..43818583827a 100644 --- a/toolkit/components/processsingleton/MainProcessSingleton.js +++ b/toolkit/components/processsingleton/MainProcessSingleton.js @@ -18,12 +18,6 @@ MainProcessSingleton.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), - logConsoleMessage(message) { - let logMsg = message.data; - logMsg.wrappedJSObject = logMsg; - Services.obs.notifyObservers(logMsg, "console-api-log-event"); - }, - // Called when a webpage calls window.external.AddSearchProvider addSearchEngine({ target: browser, data: { pageURL, engineURL } }) { pageURL = NetUtil.newURI(pageURL); @@ -73,14 +67,12 @@ MainProcessSingleton.prototype = { // before other frame scripts. Services.mm.loadFrameScript("chrome://global/content/browser-content.js", true); Services.ppmm.loadProcessScript("chrome://global/content/process-content.js", true); - Services.ppmm.addMessageListener("Console:Log", this.logConsoleMessage); Services.mm.addMessageListener("Search:AddEngine", this.addSearchEngine); Services.ppmm.loadProcessScript("resource:///modules/ContentObservers.js", true); break; } case "xpcom-shutdown": - Services.ppmm.removeMessageListener("Console:Log", this.logConsoleMessage); Services.mm.removeMessageListener("Search:AddEngine", this.addSearchEngine); break; }