Bug 1230194 Part 2 - Show stack for console evaluations that throw something, r=nchevobbe.
☠☠ backed out by 84810c2018de ☠ ☠
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 16 Apr 2019 11:51:52 -1000
changeset 531655 624da9352fd94df3517ba805863d3c0d5d6b85db
parent 531654 155bf8609915ef53445855b485668af718a77b3e
child 531656 e9a1fd460a24faaf398fc4bc3cabb31bb80b85e2
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1230194
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1230194 Part 2 - Show stack for console evaluations that throw something, r=nchevobbe. Differential Revision: https://phabricator.services.mozilla.com/D27827
devtools/client/webconsole/components/message-types/EvaluationResult.js
devtools/client/webconsole/utils/messages.js
devtools/server/actors/webconsole.js
devtools/server/actors/webconsole/utils.js
devtools/shared/specs/webconsole.js
--- a/devtools/client/webconsole/components/message-types/EvaluationResult.js
+++ b/devtools/client/webconsole/components/message-types/EvaluationResult.js
@@ -15,35 +15,42 @@ const GripMessageBody = require("devtool
 EvaluationResult.displayName = "EvaluationResult";
 
 EvaluationResult.propTypes = {
   dispatch: PropTypes.func.isRequired,
   message: PropTypes.object.isRequired,
   timestampsVisible: PropTypes.bool.isRequired,
   serviceContainer: PropTypes.object,
   maybeScrollToBottom: PropTypes.func,
+  open: PropTypes.bool,
+};
+
+EvaluationResult.defaultProps = {
+  open: false,
 };
 
 function EvaluationResult(props) {
   const {
     dispatch,
     message,
     serviceContainer,
     timestampsVisible,
     maybeScrollToBottom,
+    open,
   } = props;
 
   const {
     source,
     type,
     helperType,
     level,
     id: messageId,
     indent,
     exceptionDocURL,
+    stacktrace,
     frame,
     timeStamp,
     parameters,
     notes,
   } = message;
 
   let messageBody;
   if (typeof message.messageText !== "undefined" && message.messageText !== null) {
@@ -67,25 +74,29 @@ function EvaluationResult(props) {
       helperType,
       maybeScrollToBottom,
     });
   }
 
   const topLevelClasses = ["cm-s-mozilla"];
 
   return Message({
+    dispatch,
     source,
     type,
     level,
     indent,
     topLevelClasses,
     messageBody,
     messageId,
     serviceContainer,
     exceptionDocURL,
+    stacktrace,
+    collapsible: Array.isArray(stacktrace),
+    open,
     frame,
     timeStamp,
     parameters,
     notes,
     timestampsVisible,
     maybeScrollToBottom,
   });
 }
--- a/devtools/client/webconsole/utils/messages.js
+++ b/devtools/client/webconsole/utils/messages.js
@@ -288,16 +288,17 @@ function transformNetworkEventPacket(pac
 }
 
 function transformEvaluationResultPacket(packet) {
   let {
     exceptionMessage,
     errorMessageName,
     exceptionDocURL,
     exception,
+    exceptionStack,
     frame,
     result,
     helperResult,
     timestamp: timeStamp,
     notes,
   } = packet;
 
   const parameter = helperResult && helperResult.object
@@ -323,16 +324,17 @@ function transformEvaluationResultPacket
     source: MESSAGE_SOURCE.JAVASCRIPT,
     type: MESSAGE_TYPE.RESULT,
     helperType: helperResult ? helperResult.type : null,
     level,
     messageText: exceptionMessage,
     parameters: [parameter],
     errorMessageName,
     exceptionDocURL,
+    stacktrace: exceptionStack,
     frame,
     timeStamp,
     notes,
     private: packet.private,
   });
 }
 
 // Helpers
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1005,17 +1005,17 @@ WebConsoleActor.prototype =
     const evalInfo = evalWithDebugger(input, evalOptions, this);
 
     this.parentActor.threadActor.insideClientEvaluation = false;
 
     const evalResult = evalInfo.result;
     const helperResult = evalInfo.helperResult;
 
     let result, errorDocURL, errorMessage, errorNotes = null, errorGrip = null,
-      frame = null, awaitResult, errorMessageName;
+      frame = null, awaitResult, errorMessageName, exceptionStack;
     if (evalResult) {
       if ("return" in evalResult) {
         result = evalResult.return;
         if (
           mapped &&
           mapped.await &&
           result &&
           result.class === "Promise" &&
@@ -1024,16 +1024,31 @@ WebConsoleActor.prototype =
           awaitResult = result.unsafeDereference();
         }
       } else if ("yield" in evalResult) {
         result = evalResult.yield;
       } else if ("throw" in evalResult) {
         const error = evalResult.throw;
         errorGrip = this.createValueGrip(error);
 
+        exceptionStack = this.prepareStackForRemote(evalResult.stack);
+
+        if (exceptionStack) {
+          // Set the frame based on the topmost stack frame for the exception.
+          const {
+            filename: source,
+            sourceId,
+            lineNumber: line,
+            columnNumber: column,
+          } = exceptionStack[0];
+          frame = { source, sourceId, line, column };
+
+          exceptionStack = WebConsoleUtils.removeFramesAboveDebuggerEval(exceptionStack);
+        }
+
         errorMessage = String(error);
         if (typeof error === "object" && error !== null) {
           try {
             errorMessage = DevToolsUtils.callPropertyOnObject(error, "toString");
           } catch (e) {
             // If the debuggee is not allowed to access the "toString" property
             // of the error object, calling this property from the debuggee's
             // compartment will fail. The debugger should show the error object
@@ -1127,16 +1142,17 @@ WebConsoleActor.prototype =
       from: this.actorID,
       input: input,
       result: resultGrip,
       awaitResult,
       timestamp: timestamp,
       exception: errorGrip,
       exceptionMessage: this._createStringGrip(errorMessage),
       exceptionDocURL: errorDocURL,
+      exceptionStack,
       errorMessageName,
       frame,
       helperResult: helperResult,
       notes: errorNotes,
     };
   },
 
   /**
@@ -1461,42 +1477,55 @@ WebConsoleActor.prototype =
   },
 
   getActorIdForInternalSourceId(id) {
     const actor = this.parentActor.sources.getSourceActorByInternalSourceId(id);
     return actor ? actor.actorID : null;
   },
 
   /**
+   * Prepare a SavedFrame stack to be sent to the client.
+   *
+   * @param SavedFrame errorStack
+   *        Stack for an error we need to send to the client.
+   * @return object
+   *         The object you can send to the remote client.
+   */
+  prepareStackForRemote(errorStack) {
+    // Convert stack objects to the JSON attributes expected by client code
+    // Bug 1348885: If the global from which this error came from has been
+    // nuked, stack is going to be a dead wrapper.
+    if (!errorStack || Cu.isDeadWrapper(errorStack)) {
+      return null;
+    }
+    const stack = [];
+    let s = errorStack;
+    while (s !== null) {
+      stack.push({
+        filename: s.source,
+        sourceId: this.getActorIdForInternalSourceId(s.sourceId),
+        lineNumber: s.line,
+        columnNumber: s.column,
+        functionName: s.functionDisplayName,
+      });
+      s = s.parent;
+    }
+    return stack;
+  },
+
+  /**
    * Prepare an nsIScriptError to be sent to the client.
    *
    * @param nsIScriptError pageError
    *        The page error we need to send to the client.
    * @return object
    *         The object you can send to the remote client.
    */
   preparePageErrorForRemote: function(pageError) {
-    let stack = null;
-    // Convert stack objects to the JSON attributes expected by client code
-    // Bug 1348885: If the global from which this error came from has been
-    // nuked, stack is going to be a dead wrapper.
-    if (pageError.stack && !Cu.isDeadWrapper(pageError.stack)) {
-      stack = [];
-      let s = pageError.stack;
-      while (s !== null) {
-        stack.push({
-          filename: s.source,
-          sourceId: this.getActorIdForInternalSourceId(s.sourceId),
-          lineNumber: s.line,
-          columnNumber: s.column,
-          functionName: s.functionDisplayName,
-        });
-        s = s.parent;
-      }
-    }
+    const stack = this.prepareStackForRemote(pageError.stack);
     let lineText = pageError.sourceLine;
     if (lineText && lineText.length > DebuggerServer.LONG_STRING_INITIAL_LENGTH) {
       lineText = lineText.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
     }
 
     let notesArray = null;
     const notes = pageError.notes;
     if (notes && notes.length) {
--- a/devtools/server/actors/webconsole/utils.js
+++ b/devtools/server/actors/webconsole/utils.js
@@ -188,16 +188,39 @@ var WebConsoleUtils = {
       case "function":
         return objectWrapper(value);
       default:
         console.error("Failed to provide a grip for value of " + typeof value
                       + ": " + value);
         return null;
     }
   },
+
+  /**
+   * Remove any frames in a stack that are above a debugger-triggered evaluation
+   * and will correspond with devtools server code, which we never want to show
+   * to the user.
+   *
+   * @param array stack
+   *        An array of frames, with the topmost first, and each of which has a
+   *        'filename' property.
+   * @return array
+   *         An array of stack frames with any devtools server frames removed.
+   *         The original array is not modified.
+   */
+  removeFramesAboveDebuggerEval(stack) {
+    // Remove any frames for server code above the debugger eval.
+    const evalIndex = stack.findIndex(({ filename }) => {
+      return filename == "debugger eval code";
+    });
+    if (evalIndex != -1) {
+      return stack.slice(0, evalIndex + 1);
+    }
+    return stack;
+  },
 };
 
 exports.WebConsoleUtils = WebConsoleUtils;
 
 /**
  * WebConsole commands manager.
  *
  * Defines a set of functions /variables ("commands") that are available from
--- a/devtools/shared/specs/webconsole.js
+++ b/devtools/shared/specs/webconsole.js
@@ -44,16 +44,17 @@ const webconsoleSpecPrototype = {
     evaluationResult: {
       resultID: Option(0, "string"),
       type: "evaluationResult",
       awaitResult: Option(0, "nullable:boolean"),
       errorMessageName: Option(0, "nullable:string"),
       exception: Option(0, "nullable:json"),
       exceptionMessage: Option(0, "nullable:string"),
       exceptionDocURL: Option(0, "nullable:string"),
+      exceptionStack: Option(0, "nullable:json"),
       frame: Option(0, "nullable:json"),
       helperResult: Option(0, "nullable:json"),
       input: Option(0, "nullable:string"),
       notes: Option(0, "nullable:string"),
       result: Option(0, "nullable:json"),
       timestamp: Option(0, "string"),
       topLevelAwaitRejected: Option(0, "nullable:boolean"),
     },