Bug 1483319 Part 1 - Transmit console message arguments as debuggee values, r=jimb.
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 18 Aug 2018 05:39:01 -1000
changeset 482252 fd6f1b574cbbdc5042b1802cce4aa8e3716b5ad4
parent 482250 034adef609b91e0c2dc2c353dcb5d5dc9ff18a40
child 482253 ed84aae6166c98f9870f769193fc55a93ee27977
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersjimb
bugs1483319
milestone63.0a1
Bug 1483319 Part 1 - Transmit console message arguments as debuggee values, r=jimb.
devtools/server/actors/replay/debugger.js
devtools/server/actors/replay/replay.js
devtools/server/actors/webconsole.js
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -157,17 +157,18 @@ ReplayDebugger.prototype = {
   findScripts() {
     // Note: Debugger's findScripts() method takes a query argument, which
     // we ignore here.
     const data = this._sendRequest({ type: "findScripts" });
     return data.map(script => this._addScript(script));
   },
 
   findAllConsoleMessages() {
-    return this._sendRequest({ type: "findConsoleMessages" });
+    const messages = this._sendRequest({ type: "findConsoleMessages" });
+    return messages.map(this._convertConsoleMessage.bind(this));
   },
 
   /////////////////////////////////////////////////////////
   // ScriptSource methods
   /////////////////////////////////////////////////////////
 
   _getSource(id) {
     if (!this._scriptSources[id]) {
@@ -257,16 +258,31 @@ ReplayDebugger.prototype = {
     return this._frames[index];
   },
 
   getNewestFrame() {
     return this._getFrame(NewestFrameIndex);
   },
 
   /////////////////////////////////////////////////////////
+  // Console Message methods
+  /////////////////////////////////////////////////////////
+
+  _convertConsoleMessage(message) {
+    // Console API message arguments need conversion to debuggee values, but
+    // other contents of the message can be left alone.
+    if (message.messageType == "ConsoleAPI" && message.arguments) {
+      for (let i = 0; i < message.arguments.length; i++) {
+        message.arguments[i] = this._convertValue(message.arguments[i]);
+      }
+    }
+    return message;
+  },
+
+  /////////////////////////////////////////////////////////
   // Handlers
   /////////////////////////////////////////////////////////
 
   _getNewScript() {
     return this._addScript(this._sendRequest({ type: "getNewScript" }));
   },
 
   get onNewScript() { return this._breakpointKindGetter("NewScript"); },
@@ -301,17 +317,20 @@ ReplayDebugger.prototype = {
   get replayingOnForcedPause() {
     return this._breakpointKindGetter("ForcedPause");
   },
   set replayingOnForcedPause(handler) {
     this._breakpointKindSetter("ForcedPause", handler,
                                () => handler.call(this, this.getNewestFrame()));
   },
 
-  _getNewConsoleMessage() { return this._sendRequest({ type: "getNewConsoleMessage" }); },
+  _getNewConsoleMessage() {
+    const message = this._sendRequest({ type: "getNewConsoleMessage" });
+    return this._convertConsoleMessage(message);
+  },
 
   get onConsoleMessage() {
     return this._breakpointKindGetter("ConsoleMessage");
   },
   set onConsoleMessage(handler) {
     this._breakpointKindSetter("ConsoleMessage", handler,
                                () => handler.call(this, this._getNewConsoleMessage()));
   },
@@ -593,18 +612,18 @@ ReplayDebuggerObject.prototype = {
   get proxyTarget() { NYI(); },
   get proxyHandler() { NYI(); },
   get isPromise() { NYI(); },
   call: NYI,
   apply: NYI,
   asEnvironment: NYI,
   executeInGlobal: NYI,
   executeInGlobalWithBindings: NYI,
-  makeDebuggeeValue: NYI,
 
+  makeDebuggeeValue: NotAllowed,
   preventExtensions: NotAllowed,
   seal: NotAllowed,
   freeze: NotAllowed,
   defineProperty: NotAllowed,
   defineProperties: NotAllowed,
   deleteProperty: NotAllowed,
   forceLexicalInitializationByName: NotAllowed,
 };
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -233,25 +233,43 @@ Services.console.registerListener({
 Services.obs.addObserver({
   QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
 
   observe(message, topic, data) {
     const apiMessage = message.wrappedJSObject;
 
     const contents = {};
     for (const id in apiMessage) {
-      if (id != "wrappedJSObject") {
+      if (id != "wrappedJSObject" && id != "arguments") {
         contents[id] = JSON.parse(JSON.stringify(apiMessage[id]));
       }
     }
 
+    // Message arguments are preserved as debuggee values.
+    if (apiMessage.arguments) {
+      contents.arguments = apiMessage.arguments.map(makeDebuggeeValue);
+    }
+
     newConsoleMessage("ConsoleAPI", null, contents);
   },
 }, "console-api-log-event");
 
+function convertConsoleMessage(contents) {
+  const result = {};
+  for (const id in contents) {
+    if (id == "arguments" && contents.messageType == "ConsoleAPI") {
+      // Copy arguments over as debuggee values.
+      result.arguments = contents.arguments.map(convertValue);
+    } else {
+      result[id] = contents[id];
+    }
+  }
+  return result;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Position Handler State
 ///////////////////////////////////////////////////////////////////////////////
 
 // Position kinds we are expected to hit.
 let gPositionHandlerKinds = Object.create(null);
 
 // Handlers we tried to install but couldn't due to a script not existing.
@@ -437,16 +455,26 @@ function convertCompletionValue(value) {
     return { return: convertValue(value.return) };
   }
   if ("throw" in value) {
     return { throw: convertValue(value.throw) };
   }
   throw new Error("Unexpected completion value");
 }
 
+function makeDebuggeeValue(value) {
+  if (value && typeof value == "object") {
+    assert(!(value instanceof Debugger.Object));
+    const global = Cu.getGlobalForObject(value);
+    const dbgGlobal = dbg.makeGlobalObjectReference(global);
+    return dbgGlobal.makeDebuggeeValue(value);
+  }
+  return value;
+}
+
 // eslint-disable-next-line no-unused-vars
 function ClearPausedState() {
   gPausedObjects = new IdMap();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Handler Helpers
 ///////////////////////////////////////////////////////////////////////////////
@@ -642,21 +670,21 @@ const gRequestHandlers = {
     return convertCompletionValue(rv);
   },
 
   popFrameResult(request) {
     return gPopFrameResult ? convertCompletionValue(gPopFrameResult) : {};
   },
 
   findConsoleMessages(request) {
-    return gConsoleMessages;
+    return gConsoleMessages.map(convertConsoleMessage);
   },
 
   getNewConsoleMessage(request) {
-    return gConsoleMessages[gConsoleMessages.length - 1];
+    return convertConsoleMessage(gConsoleMessages[gConsoleMessages.length - 1]);
   },
 };
 
 // eslint-disable-next-line no-unused-vars
 function ProcessRequest(request) {
   try {
     if (gRequestHandlers[request.type]) {
       return gRequestHandlers[request.type](request);
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -417,21 +417,19 @@ WebConsoleActor.prototype =
    * @param boolean useObjectGlobal
    *        If |true| the object global is determined and added as a debuggee,
    *        otherwise |this.window| is used when makeDebuggeeValue() is invoked.
    * @return object
    *         Debuggee value for |value|.
    */
   makeDebuggeeValue: function(value, useObjectGlobal) {
     if (this.dbg.replaying) {
-      if (typeof value == "object") {
-        throw new Error("Object makeDebuggeeValue not supported with replaying debugger");
-      } else {
-        return value;
-      }
+      // If we are replaying then any values we are operating on should already
+      // be debuggee values.
+      return value;
     }
     if (useObjectGlobal && isObject(value)) {
       try {
         const global = Cu.getGlobalForObject(value);
         const dbgGlobal = this.dbg.makeGlobalObjectReference(global);
         return dbgGlobal.makeDebuggeeValue(value);
       } catch (ex) {
         // The above can throw an exception if value is not an actual object