michael@0: /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: let {Cc, Ci, Cu} = require("chrome"); michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: let { DebuggerServer, ActorPool } = require("devtools/server/main"); michael@0: // Symbols from script.js michael@0: let { ThreadActor, EnvironmentActor, ObjectActor, LongStringActor } = DebuggerServer; michael@0: michael@0: Cu.import("resource://gre/modules/jsdebugger.jsm"); michael@0: addDebuggerToGlobal(this); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Services", michael@0: "resource://gre/modules/Services.jsm"); michael@0: XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => { michael@0: return require("devtools/toolkit/webconsole/network-monitor") michael@0: .NetworkMonitor; michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(this, "NetworkMonitorChild", () => { michael@0: return require("devtools/toolkit/webconsole/network-monitor") michael@0: .NetworkMonitorChild; michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(this, "ConsoleProgressListener", () => { michael@0: return require("devtools/toolkit/webconsole/network-monitor") michael@0: .ConsoleProgressListener; michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(this, "events", () => { michael@0: return require("sdk/event/core"); michael@0: }); michael@0: michael@0: for (let name of ["WebConsoleUtils", "ConsoleServiceListener", michael@0: "ConsoleAPIListener", "JSTermHelpers", "JSPropertyProvider", michael@0: "ConsoleReflowListener"]) { michael@0: Object.defineProperty(this, name, { michael@0: get: function(prop) { michael@0: if (prop == "WebConsoleUtils") { michael@0: prop = "Utils"; michael@0: } michael@0: return require("devtools/toolkit/webconsole/utils")[prop]; michael@0: }.bind(null, name), michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * The WebConsoleActor implements capabilities needed for the Web Console michael@0: * feature. michael@0: * michael@0: * @constructor michael@0: * @param object aConnection michael@0: * The connection to the client, DebuggerServerConnection. michael@0: * @param object [aParentActor] michael@0: * Optional, the parent actor. michael@0: */ michael@0: function WebConsoleActor(aConnection, aParentActor) michael@0: { michael@0: this.conn = aConnection; michael@0: this.parentActor = aParentActor; michael@0: michael@0: this._actorPool = new ActorPool(this.conn); michael@0: this.conn.addActorPool(this._actorPool); michael@0: michael@0: this._prefs = {}; michael@0: michael@0: this.dbg = new Debugger(); michael@0: michael@0: this._netEvents = new Map(); michael@0: this._gripDepth = 0; michael@0: michael@0: this._onWillNavigate = this._onWillNavigate.bind(this); michael@0: this._onObserverNotification = this._onObserverNotification.bind(this); michael@0: if (this.parentActor.isRootActor) { michael@0: Services.obs.addObserver(this._onObserverNotification, michael@0: "last-pb-context-exited", false); michael@0: } michael@0: michael@0: this.traits = { michael@0: customNetworkRequest: !this._parentIsContentActor, michael@0: }; michael@0: } michael@0: michael@0: WebConsoleActor.l10n = new WebConsoleUtils.l10n("chrome://global/locale/console.properties"); michael@0: michael@0: WebConsoleActor.prototype = michael@0: { michael@0: /** michael@0: * Debugger instance. michael@0: * michael@0: * @see jsdebugger.jsm michael@0: */ michael@0: dbg: null, michael@0: michael@0: /** michael@0: * This is used by the ObjectActor to keep track of the depth of grip() calls. michael@0: * @private michael@0: * @type number michael@0: */ michael@0: _gripDepth: null, michael@0: michael@0: /** michael@0: * Actor pool for all of the actors we send to the client. michael@0: * @private michael@0: * @type object michael@0: * @see ActorPool michael@0: */ michael@0: _actorPool: null, michael@0: michael@0: /** michael@0: * Web Console-related preferences. michael@0: * @private michael@0: * @type object michael@0: */ michael@0: _prefs: null, michael@0: michael@0: /** michael@0: * Holds a map between nsIChannel objects and NetworkEventActors for requests michael@0: * created with sendHTTPRequest. michael@0: * michael@0: * @private michael@0: * @type Map michael@0: */ michael@0: _netEvents: null, michael@0: michael@0: /** michael@0: * The debugger server connection instance. michael@0: * @type object michael@0: */ michael@0: conn: null, michael@0: michael@0: /** michael@0: * List of supported features by the console actor. michael@0: * @type object michael@0: */ michael@0: traits: null, michael@0: michael@0: /** michael@0: * Boolean getter that tells if the parent actor is a ContentActor. michael@0: * michael@0: * @private michael@0: * @type boolean michael@0: */ michael@0: get _parentIsContentActor() { michael@0: return "ContentActor" in DebuggerServer && michael@0: this.parentActor instanceof DebuggerServer.ContentActor; michael@0: }, michael@0: michael@0: /** michael@0: * The window we work with. michael@0: * @type nsIDOMWindow michael@0: */ michael@0: get window() { michael@0: if (this.parentActor.isRootActor) { michael@0: return this._getWindowForBrowserConsole(); michael@0: } michael@0: return this.parentActor.window; michael@0: }, michael@0: michael@0: /** michael@0: * Get a window to use for the browser console. michael@0: * michael@0: * @private michael@0: * @return nsIDOMWindow michael@0: * The window to use, or null if no window could be found. michael@0: */ michael@0: _getWindowForBrowserConsole: function WCA__getWindowForBrowserConsole() michael@0: { michael@0: // Check if our last used chrome window is still live. michael@0: let window = this._lastChromeWindow && this._lastChromeWindow.get(); michael@0: // If not, look for a new one. michael@0: if (!window || window.closed) { michael@0: window = this.parentActor.window; michael@0: if (!window) { michael@0: // Try to find the Browser Console window to use instead. michael@0: window = Services.wm.getMostRecentWindow("devtools:webconsole"); michael@0: // We prefer the normal chrome window over the console window, michael@0: // so we'll look for those windows in order to replace our reference. michael@0: let onChromeWindowOpened = () => { michael@0: // We'll look for this window when someone next requests window() michael@0: Services.obs.removeObserver(onChromeWindowOpened, "domwindowopened"); michael@0: this._lastChromeWindow = null; michael@0: }; michael@0: Services.obs.addObserver(onChromeWindowOpened, "domwindowopened", false); michael@0: } michael@0: michael@0: this._handleNewWindow(window); michael@0: } michael@0: michael@0: return window; michael@0: }, michael@0: michael@0: /** michael@0: * Store a newly found window on the actor to be used in the future. michael@0: * michael@0: * @private michael@0: * @param nsIDOMWindow window michael@0: * The window to store on the actor (can be null). michael@0: */ michael@0: _handleNewWindow: function WCA__handleNewWindow(window) michael@0: { michael@0: if (window) { michael@0: if (this._hadChromeWindow) { michael@0: let contextChangedMsg = WebConsoleActor.l10n.getStr("evaluationContextChanged"); michael@0: Services.console.logStringMessage(contextChangedMsg); michael@0: } michael@0: this._lastChromeWindow = Cu.getWeakReference(window); michael@0: this._hadChromeWindow = true; michael@0: } else { michael@0: this._lastChromeWindow = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Whether we've been using a window before. michael@0: * michael@0: * @private michael@0: * @type boolean michael@0: */ michael@0: _hadChromeWindow: false, michael@0: michael@0: /** michael@0: * A weak reference to the last chrome window we used to work with. michael@0: * michael@0: * @private michael@0: * @type nsIWeakReference michael@0: */ michael@0: _lastChromeWindow: null, michael@0: michael@0: // The evalWindow is used at the scope for JS evaluation. michael@0: _evalWindow: null, michael@0: get evalWindow() { michael@0: return this._evalWindow || this.window; michael@0: }, michael@0: michael@0: set evalWindow(aWindow) { michael@0: this._evalWindow = aWindow; michael@0: michael@0: if (!this._progressListenerActive) { michael@0: events.on(this.parentActor, "will-navigate", this._onWillNavigate); michael@0: this._progressListenerActive = true; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Flag used to track if we are listening for events from the progress michael@0: * listener of the tab actor. We use the progress listener to clear michael@0: * this.evalWindow on page navigation. michael@0: * michael@0: * @private michael@0: * @type boolean michael@0: */ michael@0: _progressListenerActive: false, michael@0: michael@0: /** michael@0: * The ConsoleServiceListener instance. michael@0: * @type object michael@0: */ michael@0: consoleServiceListener: null, michael@0: michael@0: /** michael@0: * The ConsoleAPIListener instance. michael@0: */ michael@0: consoleAPIListener: null, michael@0: michael@0: /** michael@0: * The NetworkMonitor instance. michael@0: */ michael@0: networkMonitor: null, michael@0: michael@0: /** michael@0: * The ConsoleProgressListener instance. michael@0: */ michael@0: consoleProgressListener: null, michael@0: michael@0: /** michael@0: * The ConsoleReflowListener instance. michael@0: */ michael@0: consoleReflowListener: null, michael@0: michael@0: /** michael@0: * The JSTerm Helpers names cache. michael@0: * @private michael@0: * @type array michael@0: */ michael@0: _jstermHelpersCache: null, michael@0: michael@0: actorPrefix: "console", michael@0: michael@0: grip: function WCA_grip() michael@0: { michael@0: return { actor: this.actorID }; michael@0: }, michael@0: michael@0: hasNativeConsoleAPI: function WCA_hasNativeConsoleAPI(aWindow) { michael@0: let isNative = false; michael@0: try { michael@0: // We are very explicitly examining the "console" property of michael@0: // the non-Xrayed object here. michael@0: let console = aWindow.wrappedJSObject.console; michael@0: isNative = console instanceof aWindow.Console; michael@0: } michael@0: catch (ex) { } michael@0: return isNative; michael@0: }, michael@0: michael@0: _createValueGrip: ThreadActor.prototype.createValueGrip, michael@0: _stringIsLong: ThreadActor.prototype._stringIsLong, michael@0: _findProtoChain: ThreadActor.prototype._findProtoChain, michael@0: _removeFromProtoChain: ThreadActor.prototype._removeFromProtoChain, michael@0: michael@0: /** michael@0: * Destroy the current WebConsoleActor instance. michael@0: */ michael@0: disconnect: function WCA_disconnect() michael@0: { michael@0: if (this.consoleServiceListener) { michael@0: this.consoleServiceListener.destroy(); michael@0: this.consoleServiceListener = null; michael@0: } michael@0: if (this.consoleAPIListener) { michael@0: this.consoleAPIListener.destroy(); michael@0: this.consoleAPIListener = null; michael@0: } michael@0: if (this.networkMonitor) { michael@0: this.networkMonitor.destroy(); michael@0: this.networkMonitor = null; michael@0: } michael@0: if (this.consoleProgressListener) { michael@0: this.consoleProgressListener.destroy(); michael@0: this.consoleProgressListener = null; michael@0: } michael@0: if (this.consoleReflowListener) { michael@0: this.consoleReflowListener.destroy(); michael@0: this.consoleReflowListener = null; michael@0: } michael@0: this.conn.removeActorPool(this._actorPool); michael@0: if (this.parentActor.isRootActor) { michael@0: Services.obs.removeObserver(this._onObserverNotification, michael@0: "last-pb-context-exited"); michael@0: } michael@0: this._actorPool = null; michael@0: michael@0: this._jstermHelpersCache = null; michael@0: this._evalWindow = null; michael@0: this._netEvents.clear(); michael@0: this.dbg.enabled = false; michael@0: this.dbg = null; michael@0: this.conn = null; michael@0: }, michael@0: michael@0: /** michael@0: * Create and return an environment actor that corresponds to the provided michael@0: * Debugger.Environment. This is a straightforward clone of the ThreadActor's michael@0: * method except that it stores the environment actor in the web console michael@0: * actor's pool. michael@0: * michael@0: * @param Debugger.Environment aEnvironment michael@0: * The lexical environment we want to extract. michael@0: * @return The EnvironmentActor for aEnvironment or undefined for host michael@0: * functions or functions scoped to a non-debuggee global. michael@0: */ michael@0: createEnvironmentActor: function WCA_createEnvironmentActor(aEnvironment) { michael@0: if (!aEnvironment) { michael@0: return undefined; michael@0: } michael@0: michael@0: if (aEnvironment.actor) { michael@0: return aEnvironment.actor; michael@0: } michael@0: michael@0: let actor = new EnvironmentActor(aEnvironment, this); michael@0: this._actorPool.addActor(actor); michael@0: aEnvironment.actor = actor; michael@0: michael@0: return actor; michael@0: }, michael@0: michael@0: /** michael@0: * Create a grip for the given value. michael@0: * michael@0: * @param mixed aValue michael@0: * @return object michael@0: */ michael@0: createValueGrip: function WCA_createValueGrip(aValue) michael@0: { michael@0: return this._createValueGrip(aValue, this._actorPool); michael@0: }, michael@0: michael@0: /** michael@0: * Make a debuggee value for the given value. michael@0: * michael@0: * @param mixed aValue michael@0: * The value you want to get a debuggee value for. michael@0: * @param boolean aUseObjectGlobal michael@0: * If |true| the object global is determined and added as a debuggee, michael@0: * otherwise |this.window| is used when makeDebuggeeValue() is invoked. michael@0: * @return object michael@0: * Debuggee value for |aValue|. michael@0: */ michael@0: makeDebuggeeValue: function WCA_makeDebuggeeValue(aValue, aUseObjectGlobal) michael@0: { michael@0: let global = this.window; michael@0: if (aUseObjectGlobal && typeof aValue == "object") { michael@0: try { michael@0: global = Cu.getGlobalForObject(aValue); michael@0: } michael@0: catch (ex) { michael@0: // The above can throw an exception if aValue is not an actual object. michael@0: } michael@0: } michael@0: let dbgGlobal = this.dbg.makeGlobalObjectReference(global); michael@0: return dbgGlobal.makeDebuggeeValue(aValue); michael@0: }, michael@0: michael@0: /** michael@0: * Create a grip for the given object. michael@0: * michael@0: * @param object aObject michael@0: * The object you want. michael@0: * @param object aPool michael@0: * An ActorPool where the new actor instance is added. michael@0: * @param object michael@0: * The object grip. michael@0: */ michael@0: objectGrip: function WCA_objectGrip(aObject, aPool) michael@0: { michael@0: let actor = new ObjectActor(aObject, this); michael@0: aPool.addActor(actor); michael@0: return actor.grip(); michael@0: }, michael@0: michael@0: /** michael@0: * Create a grip for the given string. michael@0: * michael@0: * @param string aString michael@0: * The string you want to create the grip for. michael@0: * @param object aPool michael@0: * An ActorPool where the new actor instance is added. michael@0: * @return object michael@0: * A LongStringActor object that wraps the given string. michael@0: */ michael@0: longStringGrip: function WCA_longStringGrip(aString, aPool) michael@0: { michael@0: let actor = new LongStringActor(aString, this); michael@0: aPool.addActor(actor); michael@0: return actor.grip(); michael@0: }, michael@0: michael@0: /** michael@0: * Create a long string grip if needed for the given string. michael@0: * michael@0: * @private michael@0: * @param string aString michael@0: * The string you want to create a long string grip for. michael@0: * @return string|object michael@0: * A string is returned if |aString| is not a long string. michael@0: * A LongStringActor grip is returned if |aString| is a long string. michael@0: */ michael@0: _createStringGrip: function NEA__createStringGrip(aString) michael@0: { michael@0: if (aString && this._stringIsLong(aString)) { michael@0: return this.longStringGrip(aString, this._actorPool); michael@0: } michael@0: return aString; michael@0: }, michael@0: michael@0: /** michael@0: * Get an object actor by its ID. michael@0: * michael@0: * @param string aActorID michael@0: * @return object michael@0: */ michael@0: getActorByID: function WCA_getActorByID(aActorID) michael@0: { michael@0: return this._actorPool.get(aActorID); michael@0: }, michael@0: michael@0: /** michael@0: * Release an actor. michael@0: * michael@0: * @param object aActor michael@0: * The actor instance you want to release. michael@0: */ michael@0: releaseActor: function WCA_releaseActor(aActor) michael@0: { michael@0: this._actorPool.removeActor(aActor.actorID); michael@0: }, michael@0: michael@0: ////////////////// michael@0: // Request handlers for known packet types. michael@0: ////////////////// michael@0: michael@0: /** michael@0: * Handler for the "startListeners" request. michael@0: * michael@0: * @param object aRequest michael@0: * The JSON request object received from the Web Console client. michael@0: * @return object michael@0: * The response object which holds the startedListeners array. michael@0: */ michael@0: onStartListeners: function WCA_onStartListeners(aRequest) michael@0: { michael@0: let startedListeners = []; michael@0: let window = !this.parentActor.isRootActor ? this.window : null; michael@0: let appId = null; michael@0: let messageManager = null; michael@0: michael@0: if (this._parentIsContentActor) { michael@0: appId = this.parentActor.docShell.appId; michael@0: messageManager = this.parentActor.messageManager; michael@0: } michael@0: michael@0: while (aRequest.listeners.length > 0) { michael@0: let listener = aRequest.listeners.shift(); michael@0: switch (listener) { michael@0: case "PageError": michael@0: if (!this.consoleServiceListener) { michael@0: this.consoleServiceListener = michael@0: new ConsoleServiceListener(window, this); michael@0: this.consoleServiceListener.init(); michael@0: } michael@0: startedListeners.push(listener); michael@0: break; michael@0: case "ConsoleAPI": michael@0: if (!this.consoleAPIListener) { michael@0: this.consoleAPIListener = michael@0: new ConsoleAPIListener(window, this); michael@0: this.consoleAPIListener.init(); michael@0: } michael@0: startedListeners.push(listener); michael@0: break; michael@0: case "NetworkActivity": michael@0: if (!this.networkMonitor) { michael@0: if (appId || messageManager) { michael@0: this.networkMonitor = michael@0: new NetworkMonitorChild(appId, messageManager, michael@0: this.parentActor.actorID, this); michael@0: } michael@0: else { michael@0: this.networkMonitor = new NetworkMonitor({ window: window }, this); michael@0: } michael@0: this.networkMonitor.init(); michael@0: } michael@0: startedListeners.push(listener); michael@0: break; michael@0: case "FileActivity": michael@0: if (!this.consoleProgressListener) { michael@0: this.consoleProgressListener = michael@0: new ConsoleProgressListener(this.window, this); michael@0: } michael@0: this.consoleProgressListener.startMonitor(this.consoleProgressListener. michael@0: MONITOR_FILE_ACTIVITY); michael@0: startedListeners.push(listener); michael@0: break; michael@0: case "ReflowActivity": michael@0: if (!this.consoleReflowListener) { michael@0: this.consoleReflowListener = michael@0: new ConsoleReflowListener(this.window, this); michael@0: } michael@0: startedListeners.push(listener); michael@0: break; michael@0: } michael@0: } michael@0: return { michael@0: startedListeners: startedListeners, michael@0: nativeConsoleAPI: this.hasNativeConsoleAPI(this.window), michael@0: traits: this.traits, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Handler for the "stopListeners" request. michael@0: * michael@0: * @param object aRequest michael@0: * The JSON request object received from the Web Console client. michael@0: * @return object michael@0: * The response packet to send to the client: holds the michael@0: * stoppedListeners array. michael@0: */ michael@0: onStopListeners: function WCA_onStopListeners(aRequest) michael@0: { michael@0: let stoppedListeners = []; michael@0: michael@0: // If no specific listeners are requested to be detached, we stop all michael@0: // listeners. michael@0: let toDetach = aRequest.listeners || michael@0: ["PageError", "ConsoleAPI", "NetworkActivity", michael@0: "FileActivity"]; michael@0: michael@0: while (toDetach.length > 0) { michael@0: let listener = toDetach.shift(); michael@0: switch (listener) { michael@0: case "PageError": michael@0: if (this.consoleServiceListener) { michael@0: this.consoleServiceListener.destroy(); michael@0: this.consoleServiceListener = null; michael@0: } michael@0: stoppedListeners.push(listener); michael@0: break; michael@0: case "ConsoleAPI": michael@0: if (this.consoleAPIListener) { michael@0: this.consoleAPIListener.destroy(); michael@0: this.consoleAPIListener = null; michael@0: } michael@0: stoppedListeners.push(listener); michael@0: break; michael@0: case "NetworkActivity": michael@0: if (this.networkMonitor) { michael@0: this.networkMonitor.destroy(); michael@0: this.networkMonitor = null; michael@0: } michael@0: stoppedListeners.push(listener); michael@0: break; michael@0: case "FileActivity": michael@0: if (this.consoleProgressListener) { michael@0: this.consoleProgressListener.stopMonitor(this.consoleProgressListener. michael@0: MONITOR_FILE_ACTIVITY); michael@0: this.consoleProgressListener = null; michael@0: } michael@0: stoppedListeners.push(listener); michael@0: break; michael@0: case "ReflowActivity": michael@0: if (this.consoleReflowListener) { michael@0: this.consoleReflowListener.destroy(); michael@0: this.consoleReflowListener = null; michael@0: } michael@0: stoppedListeners.push(listener); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return { stoppedListeners: stoppedListeners }; michael@0: }, michael@0: michael@0: /** michael@0: * Handler for the "getCachedMessages" request. This method sends the cached michael@0: * error messages and the window.console API calls to the client. michael@0: * michael@0: * @param object aRequest michael@0: * The JSON request object received from the Web Console client. michael@0: * @return object michael@0: * The response packet to send to the client: it holds the cached michael@0: * messages array. michael@0: */ michael@0: onGetCachedMessages: function WCA_onGetCachedMessages(aRequest) michael@0: { michael@0: let types = aRequest.messageTypes; michael@0: if (!types) { michael@0: return { michael@0: error: "missingParameter", michael@0: message: "The messageTypes parameter is missing.", michael@0: }; michael@0: } michael@0: michael@0: let messages = []; michael@0: michael@0: while (types.length > 0) { michael@0: let type = types.shift(); michael@0: switch (type) { michael@0: case "ConsoleAPI": { michael@0: if (!this.consoleAPIListener) { michael@0: break; michael@0: } michael@0: let cache = this.consoleAPIListener michael@0: .getCachedMessages(!this.parentActor.isRootActor); michael@0: cache.forEach((aMessage) => { michael@0: let message = this.prepareConsoleMessageForRemote(aMessage); michael@0: message._type = type; michael@0: messages.push(message); michael@0: }); michael@0: break; michael@0: } michael@0: case "PageError": { michael@0: if (!this.consoleServiceListener) { michael@0: break; michael@0: } michael@0: let cache = this.consoleServiceListener michael@0: .getCachedMessages(!this.parentActor.isRootActor); michael@0: cache.forEach((aMessage) => { michael@0: let message = null; michael@0: if (aMessage instanceof Ci.nsIScriptError) { michael@0: message = this.preparePageErrorForRemote(aMessage); michael@0: message._type = type; michael@0: } michael@0: else { michael@0: message = { michael@0: _type: "LogMessage", michael@0: message: this._createStringGrip(aMessage.message), michael@0: timeStamp: aMessage.timeStamp, michael@0: }; michael@0: } michael@0: messages.push(message); michael@0: }); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; }); michael@0: michael@0: return { michael@0: from: this.actorID, michael@0: messages: messages, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Handler for the "evaluateJS" request. This method evaluates the given michael@0: * JavaScript string and sends back the result. michael@0: * michael@0: * @param object aRequest michael@0: * The JSON request object received from the Web Console client. michael@0: * @return object michael@0: * The evaluation response packet. michael@0: */ michael@0: onEvaluateJS: function WCA_onEvaluateJS(aRequest) michael@0: { michael@0: let input = aRequest.text; michael@0: let timestamp = Date.now(); michael@0: michael@0: let evalOptions = { michael@0: bindObjectActor: aRequest.bindObjectActor, michael@0: frameActor: aRequest.frameActor, michael@0: url: aRequest.url, michael@0: }; michael@0: let evalInfo = this.evalWithDebugger(input, evalOptions); michael@0: let evalResult = evalInfo.result; michael@0: let helperResult = evalInfo.helperResult; michael@0: michael@0: let result, errorMessage, errorGrip = null; michael@0: if (evalResult) { michael@0: if ("return" in evalResult) { michael@0: result = evalResult.return; michael@0: } michael@0: else if ("yield" in evalResult) { michael@0: result = evalResult.yield; michael@0: } michael@0: else if ("throw" in evalResult) { michael@0: let error = evalResult.throw; michael@0: errorGrip = this.createValueGrip(error); michael@0: let errorToString = evalInfo.window michael@0: .evalInGlobalWithBindings("ex + ''", {ex: error}); michael@0: if (errorToString && typeof errorToString.return == "string") { michael@0: errorMessage = errorToString.return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return { michael@0: from: this.actorID, michael@0: input: input, michael@0: result: this.createValueGrip(result), michael@0: timestamp: timestamp, michael@0: exception: errorGrip, michael@0: exceptionMessage: errorMessage, michael@0: helperResult: helperResult, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The Autocomplete request handler. michael@0: * michael@0: * @param object aRequest michael@0: * The request message - what input to autocomplete. michael@0: * @return object michael@0: * The response message - matched properties. michael@0: */ michael@0: onAutocomplete: function WCA_onAutocomplete(aRequest) michael@0: { michael@0: let frameActorId = aRequest.frameActor; michael@0: let dbgObject = null; michael@0: let environment = null; michael@0: michael@0: // This is the case of the paused debugger michael@0: if (frameActorId) { michael@0: let frameActor = this.conn.getActor(frameActorId); michael@0: if (frameActor) { michael@0: let frame = frameActor.frame; michael@0: environment = frame.environment; michael@0: } michael@0: else { michael@0: Cu.reportError("Web Console Actor: the frame actor was not found: " + michael@0: frameActorId); michael@0: } michael@0: } michael@0: // This is the general case (non-paused debugger) michael@0: else { michael@0: dbgObject = this.dbg.makeGlobalObjectReference(this.evalWindow); michael@0: } michael@0: michael@0: let result = JSPropertyProvider(dbgObject, environment, aRequest.text, michael@0: aRequest.cursor, frameActorId) || {}; michael@0: let matches = result.matches || []; michael@0: let reqText = aRequest.text.substr(0, aRequest.cursor); michael@0: michael@0: // We consider '$' as alphanumerc because it is used in the names of some michael@0: // helper functions. michael@0: let lastNonAlphaIsDot = /[.][a-zA-Z0-9$]*$/.test(reqText); michael@0: if (!lastNonAlphaIsDot) { michael@0: if (!this._jstermHelpersCache) { michael@0: let helpers = { michael@0: sandbox: Object.create(null) michael@0: }; michael@0: JSTermHelpers(helpers); michael@0: this._jstermHelpersCache = Object.getOwnPropertyNames(helpers.sandbox); michael@0: } michael@0: matches = matches.concat(this._jstermHelpersCache.filter(n => n.startsWith(result.matchProp))); michael@0: } michael@0: michael@0: return { michael@0: from: this.actorID, michael@0: matches: matches.sort(), michael@0: matchProp: result.matchProp, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The "clearMessagesCache" request handler. michael@0: */ michael@0: onClearMessagesCache: function WCA_onClearMessagesCache() michael@0: { michael@0: // TODO: Bug 717611 - Web Console clear button does not clear cached errors michael@0: let windowId = !this.parentActor.isRootActor ? michael@0: WebConsoleUtils.getInnerWindowId(this.window) : null; michael@0: let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"] michael@0: .getService(Ci.nsIConsoleAPIStorage); michael@0: ConsoleAPIStorage.clearEvents(windowId); michael@0: michael@0: if (this.parentActor.isRootActor) { michael@0: Services.console.logStringMessage(null); // for the Error Console michael@0: Services.console.reset(); michael@0: } michael@0: return {}; michael@0: }, michael@0: michael@0: /** michael@0: * The "getPreferences" request handler. michael@0: * michael@0: * @param object aRequest michael@0: * The request message - which preferences need to be retrieved. michael@0: * @return object michael@0: * The response message - a { key: value } object map. michael@0: */ michael@0: onGetPreferences: function WCA_onGetPreferences(aRequest) michael@0: { michael@0: let prefs = Object.create(null); michael@0: for (let key of aRequest.preferences) { michael@0: prefs[key] = !!this._prefs[key]; michael@0: } michael@0: return { preferences: prefs }; michael@0: }, michael@0: michael@0: /** michael@0: * The "setPreferences" request handler. michael@0: * michael@0: * @param object aRequest michael@0: * The request message - which preferences need to be updated. michael@0: */ michael@0: onSetPreferences: function WCA_onSetPreferences(aRequest) michael@0: { michael@0: for (let key in aRequest.preferences) { michael@0: this._prefs[key] = aRequest.preferences[key]; michael@0: michael@0: if (key == "NetworkMonitor.saveRequestAndResponseBodies" && michael@0: this.networkMonitor) { michael@0: this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key]; michael@0: } michael@0: } michael@0: return { updated: Object.keys(aRequest.preferences) }; michael@0: }, michael@0: michael@0: ////////////////// michael@0: // End of request handlers. michael@0: ////////////////// michael@0: michael@0: /** michael@0: * Create an object with the API we expose to the Web Console during michael@0: * JavaScript evaluation. michael@0: * This object inherits properties and methods from the Web Console actor. michael@0: * michael@0: * @private michael@0: * @param object aDebuggerGlobal michael@0: * A Debugger.Object that wraps a content global. This is used for the michael@0: * JSTerm helpers. michael@0: * @return object michael@0: * The same object as |this|, but with an added |sandbox| property. michael@0: * The sandbox holds methods and properties that can be used as michael@0: * bindings during JS evaluation. michael@0: */ michael@0: _getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerGlobal) michael@0: { michael@0: let helpers = { michael@0: window: this.evalWindow, michael@0: chromeWindow: this.chromeWindow.bind(this), michael@0: makeDebuggeeValue: aDebuggerGlobal.makeDebuggeeValue.bind(aDebuggerGlobal), michael@0: createValueGrip: this.createValueGrip.bind(this), michael@0: sandbox: Object.create(null), michael@0: helperResult: null, michael@0: consoleActor: this, michael@0: }; michael@0: JSTermHelpers(helpers); michael@0: michael@0: // Make sure the helpers can be used during eval. michael@0: for (let name in helpers.sandbox) { michael@0: let desc = Object.getOwnPropertyDescriptor(helpers.sandbox, name); michael@0: if (desc.get || desc.set) { michael@0: continue; michael@0: } michael@0: helpers.sandbox[name] = aDebuggerGlobal.makeDebuggeeValue(desc.value); michael@0: } michael@0: return helpers; michael@0: }, michael@0: michael@0: /** michael@0: * Evaluates a string using the debugger API. michael@0: * michael@0: * To allow the variables view to update properties from the Web Console we michael@0: * provide the "bindObjectActor" mechanism: the Web Console tells the michael@0: * ObjectActor ID for which it desires to evaluate an expression. The michael@0: * Debugger.Object pointed at by the actor ID is bound such that it is michael@0: * available during expression evaluation (evalInGlobalWithBindings()). michael@0: * michael@0: * Example: michael@0: * _self['foobar'] = 'test' michael@0: * where |_self| refers to the desired object. michael@0: * michael@0: * The |frameActor| property allows the Web Console client to provide the michael@0: * frame actor ID, such that the expression can be evaluated in the michael@0: * user-selected stack frame. michael@0: * michael@0: * For the above to work we need the debugger and the Web Console to share michael@0: * a connection, otherwise the Web Console actor will not find the frame michael@0: * actor. michael@0: * michael@0: * The Debugger.Frame comes from the jsdebugger's Debugger instance, which michael@0: * is different from the Web Console's Debugger instance. This means that michael@0: * for evaluation to work, we need to create a new instance for the jsterm michael@0: * helpers - they need to be Debugger.Objects coming from the jsdebugger's michael@0: * Debugger instance. michael@0: * michael@0: * When |bindObjectActor| is used objects can come from different iframes, michael@0: * from different domains. To avoid permission-related errors when objects michael@0: * come from a different window, we also determine the object's own global, michael@0: * such that evaluation happens in the context of that global. This means that michael@0: * evaluation will happen in the object's iframe, rather than the top level michael@0: * window. michael@0: * michael@0: * @param string aString michael@0: * String to evaluate. michael@0: * @param object [aOptions] michael@0: * Options for evaluation: michael@0: * - bindObjectActor: the ObjectActor ID to use for evaluation. michael@0: * |evalWithBindings()| will be called with one additional binding: michael@0: * |_self| which will point to the Debugger.Object of the given michael@0: * ObjectActor. michael@0: * - frameActor: the FrameActor ID to use for evaluation. The given michael@0: * debugger frame is used for evaluation, instead of the global window. michael@0: * @return object michael@0: * An object that holds the following properties: michael@0: * - dbg: the debugger where the string was evaluated. michael@0: * - frame: (optional) the frame where the string was evaluated. michael@0: * - window: the Debugger.Object for the global where the string was michael@0: * evaluated. michael@0: * - result: the result of the evaluation. michael@0: * - helperResult: any result coming from a JSTerm helper function. michael@0: * - url: the url to evaluate the script as. Defaults to michael@0: * "debugger eval code". michael@0: */ michael@0: evalWithDebugger: function WCA_evalWithDebugger(aString, aOptions = {}) michael@0: { michael@0: // The help function needs to be easy to guess, so we make the () optional. michael@0: if (aString.trim() == "help" || aString.trim() == "?") { michael@0: aString = "help()"; michael@0: } michael@0: michael@0: // Find the Debugger.Frame of the given FrameActor. michael@0: let frame = null, frameActor = null; michael@0: if (aOptions.frameActor) { michael@0: frameActor = this.conn.getActor(aOptions.frameActor); michael@0: if (frameActor) { michael@0: frame = frameActor.frame; michael@0: } michael@0: else { michael@0: Cu.reportError("Web Console Actor: the frame actor was not found: " + michael@0: aOptions.frameActor); michael@0: } michael@0: } michael@0: michael@0: // If we've been given a frame actor in whose scope we should evaluate the michael@0: // expression, be sure to use that frame's Debugger (that is, the JavaScript michael@0: // debugger's Debugger) for the whole operation, not the console's Debugger. michael@0: // (One Debugger will treat a different Debugger's Debugger.Object instances michael@0: // as ordinary objects, not as references to be followed, so mixing michael@0: // debuggers causes strange behaviors.) michael@0: let dbg = frame ? frameActor.threadActor.dbg : this.dbg; michael@0: let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow); michael@0: michael@0: // If we have an object to bind to |_self|, create a Debugger.Object michael@0: // referring to that object, belonging to dbg. michael@0: let bindSelf = null; michael@0: let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow); michael@0: if (aOptions.bindObjectActor) { michael@0: let objActor = this.getActorByID(aOptions.bindObjectActor); michael@0: if (objActor) { michael@0: let jsObj = objActor.obj.unsafeDereference(); michael@0: // If we use the makeDebuggeeValue method of jsObj's own global, then michael@0: // we'll get a D.O that sees jsObj as viewed from its own compartment - michael@0: // that is, without wrappers. The evalWithBindings call will then wrap michael@0: // jsObj appropriately for the evaluation compartment. michael@0: let global = Cu.getGlobalForObject(jsObj); michael@0: dbgWindow = dbg.makeGlobalObjectReference(global); michael@0: bindSelf = dbgWindow.makeDebuggeeValue(jsObj); michael@0: } michael@0: } michael@0: michael@0: // Get the JSTerm helpers for the given debugger window. michael@0: let helpers = this._getJSTermHelpers(dbgWindow); michael@0: let bindings = helpers.sandbox; michael@0: if (bindSelf) { michael@0: bindings._self = bindSelf; michael@0: } michael@0: michael@0: // Check if the Debugger.Frame or Debugger.Object for the global include michael@0: // $ or $$. We will not overwrite these functions with the jsterm helpers. michael@0: let found$ = false, found$$ = false; michael@0: if (frame) { michael@0: let env = frame.environment; michael@0: if (env) { michael@0: found$ = !!env.find("$"); michael@0: found$$ = !!env.find("$$"); michael@0: } michael@0: } michael@0: else { michael@0: found$ = !!dbgWindow.getOwnPropertyDescriptor("$"); michael@0: found$$ = !!dbgWindow.getOwnPropertyDescriptor("$$"); michael@0: } michael@0: michael@0: let $ = null, $$ = null; michael@0: if (found$) { michael@0: $ = bindings.$; michael@0: delete bindings.$; michael@0: } michael@0: if (found$$) { michael@0: $$ = bindings.$$; michael@0: delete bindings.$$; michael@0: } michael@0: michael@0: // Ready to evaluate the string. michael@0: helpers.evalInput = aString; michael@0: michael@0: let evalOptions; michael@0: if (typeof aOptions.url == "string") { michael@0: evalOptions = { url: aOptions.url }; michael@0: } michael@0: michael@0: let result; michael@0: if (frame) { michael@0: result = frame.evalWithBindings(aString, bindings, evalOptions); michael@0: } michael@0: else { michael@0: result = dbgWindow.evalInGlobalWithBindings(aString, bindings, evalOptions); michael@0: } michael@0: michael@0: let helperResult = helpers.helperResult; michael@0: delete helpers.evalInput; michael@0: delete helpers.helperResult; michael@0: michael@0: if ($) { michael@0: bindings.$ = $; michael@0: } michael@0: if ($$) { michael@0: bindings.$$ = $$; michael@0: } michael@0: michael@0: if (bindings._self) { michael@0: delete bindings._self; michael@0: } michael@0: michael@0: return { michael@0: result: result, michael@0: helperResult: helperResult, michael@0: dbg: dbg, michael@0: frame: frame, michael@0: window: dbgWindow, michael@0: }; michael@0: }, michael@0: michael@0: ////////////////// michael@0: // Event handlers for various listeners. michael@0: ////////////////// michael@0: michael@0: /** michael@0: * Handler for messages received from the ConsoleServiceListener. This method michael@0: * sends the nsIConsoleMessage to the remote Web Console client. michael@0: * michael@0: * @param nsIConsoleMessage aMessage michael@0: * The message we need to send to the client. michael@0: */ michael@0: onConsoleServiceMessage: function WCA_onConsoleServiceMessage(aMessage) michael@0: { michael@0: let packet; michael@0: if (aMessage instanceof Ci.nsIScriptError) { michael@0: packet = { michael@0: from: this.actorID, michael@0: type: "pageError", michael@0: pageError: this.preparePageErrorForRemote(aMessage), michael@0: }; michael@0: } michael@0: else { michael@0: packet = { michael@0: from: this.actorID, michael@0: type: "logMessage", michael@0: message: this._createStringGrip(aMessage.message), michael@0: timeStamp: aMessage.timeStamp, michael@0: }; michael@0: } michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Prepare an nsIScriptError to be sent to the client. michael@0: * michael@0: * @param nsIScriptError aPageError michael@0: * The page error we need to send to the client. michael@0: * @return object michael@0: * The object you can send to the remote client. michael@0: */ michael@0: preparePageErrorForRemote: function WCA_preparePageErrorForRemote(aPageError) michael@0: { michael@0: let lineText = aPageError.sourceLine; michael@0: if (lineText && lineText.length > DebuggerServer.LONG_STRING_INITIAL_LENGTH) { michael@0: lineText = lineText.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH); michael@0: } michael@0: michael@0: return { michael@0: errorMessage: this._createStringGrip(aPageError.errorMessage), michael@0: sourceName: aPageError.sourceName, michael@0: lineText: lineText, michael@0: lineNumber: aPageError.lineNumber, michael@0: columnNumber: aPageError.columnNumber, michael@0: category: aPageError.category, michael@0: timeStamp: aPageError.timeStamp, michael@0: warning: !!(aPageError.flags & aPageError.warningFlag), michael@0: error: !!(aPageError.flags & aPageError.errorFlag), michael@0: exception: !!(aPageError.flags & aPageError.exceptionFlag), michael@0: strict: !!(aPageError.flags & aPageError.strictFlag), michael@0: private: aPageError.isFromPrivateWindow, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Handler for window.console API calls received from the ConsoleAPIListener. michael@0: * This method sends the object to the remote Web Console client. michael@0: * michael@0: * @see ConsoleAPIListener michael@0: * @param object aMessage michael@0: * The console API call we need to send to the remote client. michael@0: */ michael@0: onConsoleAPICall: function WCA_onConsoleAPICall(aMessage) michael@0: { michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "consoleAPICall", michael@0: message: this.prepareConsoleMessageForRemote(aMessage), michael@0: }; michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Handler for network events. This method is invoked when a new network event michael@0: * is about to be recorded. michael@0: * michael@0: * @see NetworkEventActor michael@0: * @see NetworkMonitor from webconsole/utils.js michael@0: * michael@0: * @param object aEvent michael@0: * The initial network request event information. michael@0: * @param nsIHttpChannel aChannel michael@0: * The network request nsIHttpChannel object. michael@0: * @return object michael@0: * A new NetworkEventActor is returned. This is used for tracking the michael@0: * network request and response. michael@0: */ michael@0: onNetworkEvent: function WCA_onNetworkEvent(aEvent, aChannel) michael@0: { michael@0: let actor = this.getNetworkEventActor(aChannel); michael@0: actor.init(aEvent); michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEvent", michael@0: eventActor: actor.grip(), michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: michael@0: return actor; michael@0: }, michael@0: michael@0: /** michael@0: * Get the NetworkEventActor for a nsIChannel, if it exists, michael@0: * otherwise create a new one. michael@0: * michael@0: * @param nsIHttpChannel aChannel michael@0: * The channel for the network event. michael@0: * @return object michael@0: * The NetworkEventActor for the given channel. michael@0: */ michael@0: getNetworkEventActor: function WCA_getNetworkEventActor(aChannel) { michael@0: let actor = this._netEvents.get(aChannel); michael@0: if (actor) { michael@0: // delete from map as we should only need to do this check once michael@0: this._netEvents.delete(aChannel); michael@0: actor.channel = null; michael@0: return actor; michael@0: } michael@0: michael@0: actor = new NetworkEventActor(aChannel, this); michael@0: this._actorPool.addActor(actor); michael@0: return actor; michael@0: }, michael@0: michael@0: /** michael@0: * Send a new HTTP request from the target's window. michael@0: * michael@0: * @param object aMessage michael@0: * Object with 'request' - the HTTP request details. michael@0: */ michael@0: onSendHTTPRequest: function WCA_onSendHTTPRequest(aMessage) michael@0: { michael@0: let details = aMessage.request; michael@0: michael@0: // send request from target's window michael@0: let request = new this.window.XMLHttpRequest(); michael@0: request.open(details.method, details.url, true); michael@0: michael@0: for (let {name, value} of details.headers) { michael@0: request.setRequestHeader(name, value); michael@0: } michael@0: request.send(details.body); michael@0: michael@0: let actor = this.getNetworkEventActor(request.channel); michael@0: michael@0: // map channel to actor so we can associate future events with it michael@0: this._netEvents.set(request.channel, actor); michael@0: michael@0: return { michael@0: from: this.actorID, michael@0: eventActor: actor.grip() michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Handler for file activity. This method sends the file request information michael@0: * to the remote Web Console client. michael@0: * michael@0: * @see ConsoleProgressListener michael@0: * @param string aFileURI michael@0: * The requested file URI. michael@0: */ michael@0: onFileActivity: function WCA_onFileActivity(aFileURI) michael@0: { michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "fileActivity", michael@0: uri: aFileURI, michael@0: }; michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Handler for reflow activity. This method forwards reflow events to the michael@0: * remote Web Console client. michael@0: * michael@0: * @see ConsoleReflowListener michael@0: * @param Object aReflowInfo michael@0: */ michael@0: onReflowActivity: function WCA_onReflowActivity(aReflowInfo) michael@0: { michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "reflowActivity", michael@0: interruptible: aReflowInfo.interruptible, michael@0: start: aReflowInfo.start, michael@0: end: aReflowInfo.end, michael@0: sourceURL: aReflowInfo.sourceURL, michael@0: sourceLine: aReflowInfo.sourceLine, michael@0: functionName: aReflowInfo.functionName michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: ////////////////// michael@0: // End of event handlers for various listeners. michael@0: ////////////////// michael@0: michael@0: /** michael@0: * Prepare a message from the console API to be sent to the remote Web Console michael@0: * instance. michael@0: * michael@0: * @param object aMessage michael@0: * The original message received from console-api-log-event. michael@0: * @return object michael@0: * The object that can be sent to the remote client. michael@0: */ michael@0: prepareConsoleMessageForRemote: michael@0: function WCA_prepareConsoleMessageForRemote(aMessage) michael@0: { michael@0: let result = WebConsoleUtils.cloneObject(aMessage); michael@0: delete result.wrappedJSObject; michael@0: delete result.ID; michael@0: delete result.innerID; michael@0: michael@0: result.arguments = Array.map(aMessage.arguments || [], (aObj) => { michael@0: let dbgObj = this.makeDebuggeeValue(aObj, true); michael@0: return this.createValueGrip(dbgObj); michael@0: }); michael@0: michael@0: result.styles = Array.map(aMessage.styles || [], (aString) => { michael@0: return this.createValueGrip(aString); michael@0: }); michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Find the XUL window that owns the content window. michael@0: * michael@0: * @return Window michael@0: * The XUL window that owns the content window. michael@0: */ michael@0: chromeWindow: function WCA_chromeWindow() michael@0: { michael@0: let window = null; michael@0: try { michael@0: window = this.window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell) michael@0: .chromeEventHandler.ownerDocument.defaultView; michael@0: } michael@0: catch (ex) { michael@0: // The above can fail because chromeEventHandler is not available for all michael@0: // kinds of |this.window|. michael@0: } michael@0: michael@0: return window; michael@0: }, michael@0: michael@0: /** michael@0: * Notification observer for the "last-pb-context-exited" topic. michael@0: * michael@0: * @private michael@0: * @param object aSubject michael@0: * Notification subject - in this case it is the inner window ID that michael@0: * was destroyed. michael@0: * @param string aTopic michael@0: * Notification topic. michael@0: */ michael@0: _onObserverNotification: function WCA__onObserverNotification(aSubject, aTopic) michael@0: { michael@0: switch (aTopic) { michael@0: case "last-pb-context-exited": michael@0: this.conn.send({ michael@0: from: this.actorID, michael@0: type: "lastPrivateContextExited", michael@0: }); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The "will-navigate" progress listener. This is used to clear the current michael@0: * eval scope. michael@0: */ michael@0: _onWillNavigate: function WCA__onWillNavigate({ window, isTopLevel }) michael@0: { michael@0: if (isTopLevel) { michael@0: this._evalWindow = null; michael@0: events.off(this.parentActor, "will-navigate", this._onWillNavigate); michael@0: this._progressListenerActive = false; michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: WebConsoleActor.prototype.requestTypes = michael@0: { michael@0: startListeners: WebConsoleActor.prototype.onStartListeners, michael@0: stopListeners: WebConsoleActor.prototype.onStopListeners, michael@0: getCachedMessages: WebConsoleActor.prototype.onGetCachedMessages, michael@0: evaluateJS: WebConsoleActor.prototype.onEvaluateJS, michael@0: autocomplete: WebConsoleActor.prototype.onAutocomplete, michael@0: clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache, michael@0: getPreferences: WebConsoleActor.prototype.onGetPreferences, michael@0: setPreferences: WebConsoleActor.prototype.onSetPreferences, michael@0: sendHTTPRequest: WebConsoleActor.prototype.onSendHTTPRequest michael@0: }; michael@0: michael@0: /** michael@0: * Creates an actor for a network event. michael@0: * michael@0: * @constructor michael@0: * @param object aChannel michael@0: * The nsIChannel associated with this event. michael@0: * @param object aWebConsoleActor michael@0: * The parent WebConsoleActor instance for this object. michael@0: */ michael@0: function NetworkEventActor(aChannel, aWebConsoleActor) michael@0: { michael@0: this.parent = aWebConsoleActor; michael@0: this.conn = this.parent.conn; michael@0: this.channel = aChannel; michael@0: michael@0: this._request = { michael@0: method: null, michael@0: url: null, michael@0: httpVersion: null, michael@0: headers: [], michael@0: cookies: [], michael@0: headersSize: null, michael@0: postData: {}, michael@0: }; michael@0: michael@0: this._response = { michael@0: headers: [], michael@0: cookies: [], michael@0: content: {}, michael@0: }; michael@0: michael@0: this._timings = {}; michael@0: michael@0: // Keep track of LongStringActors owned by this NetworkEventActor. michael@0: this._longStringActors = new Set(); michael@0: } michael@0: michael@0: NetworkEventActor.prototype = michael@0: { michael@0: _request: null, michael@0: _response: null, michael@0: _timings: null, michael@0: _longStringActors: null, michael@0: michael@0: actorPrefix: "netEvent", michael@0: michael@0: /** michael@0: * Returns a grip for this actor for returning in a protocol message. michael@0: */ michael@0: grip: function NEA_grip() michael@0: { michael@0: return { michael@0: actor: this.actorID, michael@0: startedDateTime: this._startedDateTime, michael@0: url: this._request.url, michael@0: method: this._request.method, michael@0: isXHR: this._isXHR, michael@0: private: this._private, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Releases this actor from the pool. michael@0: */ michael@0: release: function NEA_release() michael@0: { michael@0: for (let grip of this._longStringActors) { michael@0: let actor = this.parent.getActorByID(grip.actor); michael@0: if (actor) { michael@0: this.parent.releaseActor(actor); michael@0: } michael@0: } michael@0: this._longStringActors = new Set(); michael@0: michael@0: if (this.channel) { michael@0: this.parent._netEvents.delete(this.channel); michael@0: } michael@0: this.parent.releaseActor(this); michael@0: }, michael@0: michael@0: /** michael@0: * Handle a protocol request to release a grip. michael@0: */ michael@0: onRelease: function NEA_onRelease() michael@0: { michael@0: this.release(); michael@0: return {}; michael@0: }, michael@0: michael@0: /** michael@0: * Set the properties of this actor based on it's corresponding michael@0: * network event. michael@0: * michael@0: * @param object aNetworkEvent michael@0: * The network event associated with this actor. michael@0: */ michael@0: init: function NEA_init(aNetworkEvent) michael@0: { michael@0: this._startedDateTime = aNetworkEvent.startedDateTime; michael@0: this._isXHR = aNetworkEvent.isXHR; michael@0: michael@0: for (let prop of ['method', 'url', 'httpVersion', 'headersSize']) { michael@0: this._request[prop] = aNetworkEvent[prop]; michael@0: } michael@0: michael@0: this._discardRequestBody = aNetworkEvent.discardRequestBody; michael@0: this._discardResponseBody = aNetworkEvent.discardResponseBody; michael@0: this._private = aNetworkEvent.private; michael@0: }, michael@0: michael@0: /** michael@0: * The "getRequestHeaders" packet type handler. michael@0: * michael@0: * @return object michael@0: * The response packet - network request headers. michael@0: */ michael@0: onGetRequestHeaders: function NEA_onGetRequestHeaders() michael@0: { michael@0: return { michael@0: from: this.actorID, michael@0: headers: this._request.headers, michael@0: headersSize: this._request.headersSize, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The "getRequestCookies" packet type handler. michael@0: * michael@0: * @return object michael@0: * The response packet - network request cookies. michael@0: */ michael@0: onGetRequestCookies: function NEA_onGetRequestCookies() michael@0: { michael@0: return { michael@0: from: this.actorID, michael@0: cookies: this._request.cookies, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The "getRequestPostData" packet type handler. michael@0: * michael@0: * @return object michael@0: * The response packet - network POST data. michael@0: */ michael@0: onGetRequestPostData: function NEA_onGetRequestPostData() michael@0: { michael@0: return { michael@0: from: this.actorID, michael@0: postData: this._request.postData, michael@0: postDataDiscarded: this._discardRequestBody, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The "getResponseHeaders" packet type handler. michael@0: * michael@0: * @return object michael@0: * The response packet - network response headers. michael@0: */ michael@0: onGetResponseHeaders: function NEA_onGetResponseHeaders() michael@0: { michael@0: return { michael@0: from: this.actorID, michael@0: headers: this._response.headers, michael@0: headersSize: this._response.headersSize, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The "getResponseCookies" packet type handler. michael@0: * michael@0: * @return object michael@0: * The response packet - network response cookies. michael@0: */ michael@0: onGetResponseCookies: function NEA_onGetResponseCookies() michael@0: { michael@0: return { michael@0: from: this.actorID, michael@0: cookies: this._response.cookies, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The "getResponseContent" packet type handler. michael@0: * michael@0: * @return object michael@0: * The response packet - network response content. michael@0: */ michael@0: onGetResponseContent: function NEA_onGetResponseContent() michael@0: { michael@0: return { michael@0: from: this.actorID, michael@0: content: this._response.content, michael@0: contentDiscarded: this._discardResponseBody, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * The "getEventTimings" packet type handler. michael@0: * michael@0: * @return object michael@0: * The response packet - network event timings. michael@0: */ michael@0: onGetEventTimings: function NEA_onGetEventTimings() michael@0: { michael@0: return { michael@0: from: this.actorID, michael@0: timings: this._timings, michael@0: totalTime: this._totalTime, michael@0: }; michael@0: }, michael@0: michael@0: /****************************************************************** michael@0: * Listeners for new network event data coming from NetworkMonitor. michael@0: ******************************************************************/ michael@0: michael@0: /** michael@0: * Add network request headers. michael@0: * michael@0: * @param array aHeaders michael@0: * The request headers array. michael@0: */ michael@0: addRequestHeaders: function NEA_addRequestHeaders(aHeaders) michael@0: { michael@0: this._request.headers = aHeaders; michael@0: this._prepareHeaders(aHeaders); michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "requestHeaders", michael@0: headers: aHeaders.length, michael@0: headersSize: this._request.headersSize, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Add network request cookies. michael@0: * michael@0: * @param array aCookies michael@0: * The request cookies array. michael@0: */ michael@0: addRequestCookies: function NEA_addRequestCookies(aCookies) michael@0: { michael@0: this._request.cookies = aCookies; michael@0: this._prepareHeaders(aCookies); michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "requestCookies", michael@0: cookies: aCookies.length, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Add network request POST data. michael@0: * michael@0: * @param object aPostData michael@0: * The request POST data. michael@0: */ michael@0: addRequestPostData: function NEA_addRequestPostData(aPostData) michael@0: { michael@0: this._request.postData = aPostData; michael@0: aPostData.text = this.parent._createStringGrip(aPostData.text); michael@0: if (typeof aPostData.text == "object") { michael@0: this._longStringActors.add(aPostData.text); michael@0: } michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "requestPostData", michael@0: dataSize: aPostData.text.length, michael@0: discardRequestBody: this._discardRequestBody, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Add the initial network response information. michael@0: * michael@0: * @param object aInfo michael@0: * The response information. michael@0: */ michael@0: addResponseStart: function NEA_addResponseStart(aInfo) michael@0: { michael@0: this._response.httpVersion = aInfo.httpVersion; michael@0: this._response.status = aInfo.status; michael@0: this._response.statusText = aInfo.statusText; michael@0: this._response.headersSize = aInfo.headersSize; michael@0: this._discardResponseBody = aInfo.discardResponseBody; michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "responseStart", michael@0: response: aInfo, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Add network response headers. michael@0: * michael@0: * @param array aHeaders michael@0: * The response headers array. michael@0: */ michael@0: addResponseHeaders: function NEA_addResponseHeaders(aHeaders) michael@0: { michael@0: this._response.headers = aHeaders; michael@0: this._prepareHeaders(aHeaders); michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "responseHeaders", michael@0: headers: aHeaders.length, michael@0: headersSize: this._response.headersSize, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Add network response cookies. michael@0: * michael@0: * @param array aCookies michael@0: * The response cookies array. michael@0: */ michael@0: addResponseCookies: function NEA_addResponseCookies(aCookies) michael@0: { michael@0: this._response.cookies = aCookies; michael@0: this._prepareHeaders(aCookies); michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "responseCookies", michael@0: cookies: aCookies.length, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Add network response content. michael@0: * michael@0: * @param object aContent michael@0: * The response content. michael@0: * @param boolean aDiscardedResponseBody michael@0: * Tells if the response content was recorded or not. michael@0: */ michael@0: addResponseContent: michael@0: function NEA_addResponseContent(aContent, aDiscardedResponseBody) michael@0: { michael@0: this._response.content = aContent; michael@0: aContent.text = this.parent._createStringGrip(aContent.text); michael@0: if (typeof aContent.text == "object") { michael@0: this._longStringActors.add(aContent.text); michael@0: } michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "responseContent", michael@0: mimeType: aContent.mimeType, michael@0: contentSize: aContent.text.length, michael@0: discardResponseBody: aDiscardedResponseBody, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Add network event timing information. michael@0: * michael@0: * @param number aTotal michael@0: * The total time of the network event. michael@0: * @param object aTimings michael@0: * Timing details about the network event. michael@0: */ michael@0: addEventTimings: function NEA_addEventTimings(aTotal, aTimings) michael@0: { michael@0: this._totalTime = aTotal; michael@0: this._timings = aTimings; michael@0: michael@0: let packet = { michael@0: from: this.actorID, michael@0: type: "networkEventUpdate", michael@0: updateType: "eventTimings", michael@0: totalTime: aTotal, michael@0: }; michael@0: michael@0: this.conn.send(packet); michael@0: }, michael@0: michael@0: /** michael@0: * Prepare the headers array to be sent to the client by using the michael@0: * LongStringActor for the header values, when needed. michael@0: * michael@0: * @private michael@0: * @param array aHeaders michael@0: */ michael@0: _prepareHeaders: function NEA__prepareHeaders(aHeaders) michael@0: { michael@0: for (let header of aHeaders) { michael@0: header.value = this.parent._createStringGrip(header.value); michael@0: if (typeof header.value == "object") { michael@0: this._longStringActors.add(header.value); michael@0: } michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: NetworkEventActor.prototype.requestTypes = michael@0: { michael@0: "release": NetworkEventActor.prototype.onRelease, michael@0: "getRequestHeaders": NetworkEventActor.prototype.onGetRequestHeaders, michael@0: "getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies, michael@0: "getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData, michael@0: "getResponseHeaders": NetworkEventActor.prototype.onGetResponseHeaders, michael@0: "getResponseCookies": NetworkEventActor.prototype.onGetResponseCookies, michael@0: "getResponseContent": NetworkEventActor.prototype.onGetResponseContent, michael@0: "getEventTimings": NetworkEventActor.prototype.onGetEventTimings, michael@0: }; michael@0: michael@0: exports.register = function(handle) { michael@0: handle.addGlobalActor(WebConsoleActor, "consoleActor"); michael@0: handle.addTabActor(WebConsoleActor, "consoleActor"); michael@0: }; michael@0: michael@0: exports.unregister = function(handle) { michael@0: handle.removeGlobalActor(WebConsoleActor, "consoleActor"); michael@0: handle.removeTabActor(WebConsoleActor, "consoleActor"); michael@0: };