Bug 1224073 - Instead of unsafeDereference to get the string of an error in a worker, evaluate it in Debugger;r=ejpbruel draft
authorBrian Grinstead <bgrinstead@mozilla.com>
Fri, 26 Aug 2016 10:16:11 -0700
changeset 406245 3fb95beec098724b5561c92fd25ab48cfc4ef764
parent 405571 c0c2f6077694008794c06900943654db0143c2b9
child 529600 314c8d87f5c3b839ffe0094a7325701540eab966
push id27663
push userbgrinstead@mozilla.com
push dateFri, 26 Aug 2016 17:16:51 +0000
reviewersejpbruel
bugs1224073
milestone51.0a1
Bug 1224073 - Instead of unsafeDereference to get the string of an error in a worker, evaluate it in Debugger;r=ejpbruel MozReview-Commit-ID: 3juau0t3mus
devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js
devtools/server/actors/webconsole.js
--- a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js
@@ -38,18 +38,17 @@ add_task(function* testWhilePaused() {
 
   info("Trying to get the result of command2");
   executed = yield command2;
   ok(executed.textContent.includes("10003"),
       "command2 executed successfully");
 
   info("Trying to get the result of command3");
   executed = yield command3;
-  // XXXworkers This is failing until Bug 1215120 is resolved.
-  todo(executed.textContent.includes("ReferenceError: foobar is not defined"),
+  ok(executed.textContent.includes("ReferenceError: foobar is not defined"),
       "command3 executed successfully");
 
   let onceResumed = gTarget.once("thread-resumed");
   EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
   yield onceResumed;
 
   terminateWorkerInTab(tab, WORKER_URL);
   yield waitForWorkerClose(workerClient);
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -893,26 +893,32 @@ WebConsoleActor.prototype =
     if (evalResult) {
       if ("return" in evalResult) {
         result = evalResult.return;
       } else if ("yield" in evalResult) {
         result = evalResult.yield;
       } else if ("throw" in evalResult) {
         let error = evalResult.throw;
         errorGrip = this.createValueGrip(error);
-        // XXXworkers: Calling unsafeDereference() returns an object with no
-        // toString method in workers. See Bug 1215120.
-        let unsafeDereference = error && (typeof error === "object") &&
-                                error.unsafeDereference();
-        errorMessage = unsafeDereference && unsafeDereference.toString
-          ? unsafeDereference.toString()
-          : String(error);
+        errorMessage = String(error);
 
-          // It is possible that we won't have permission to unwrap an
-          // object and retrieve its errorMessageName.
+        try {
+          errorMessage = error.unsafeDereference().toString();
+        } catch(e) {
+          // We aren't able to access unsafeDereference inside worker thread, so
+          // use the Debugger to call toString on the object. See Bug 1224073.
+          let { frame, dbg } = this.getEvalDebuggerInfo(aRequest.frameActor);
+          let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow);
+          let evalErrorString = dbgWindow.executeInGlobalWithBindings("_self.toString()",
+                                                                      {_self: error});
+          if (evalErrorString.return) {
+            errorMessage = evalErrorString.return;
+          }
+        }
+
         try {
           errorDocURL = ErrorDocs.GetURL(error);
         } catch (ex) {}
       }
     }
 
     // If a value is encountered that the debugger server doesn't support yet,
     // the console should remain functional.
@@ -1133,16 +1139,52 @@ WebConsoleActor.prototype =
         desc.value = aDebuggerGlobal.makeDebuggeeValue(desc.value);
       }
       Object.defineProperty(helpers.sandbox, name, desc);
     }
     return helpers;
   },
 
   /**
+   * Get debugger / frame information needed to evaluate code with an optional
+   * frame.
+   *
+   * @param string frameActorID
+   *        The actor for a frame (null if execution should happen in global)
+   * @return object
+   *         An object that holds the following properties:
+   *         - dbg: the debugger where code should be evaluated.
+   *         - frame: the frame where code should be evaluated (can be null)
+   */
+  getEvalDebuggerInfo(frameActorID = null) {
+    // Find the Debugger.Frame of the given FrameActor.
+    let frame = null, frameActor = null;
+    if (frameActorID) {
+      frameActor = this.conn.getActor(frameActorID);
+      if (frameActor) {
+        frame = frameActor.frame;
+      }
+      else {
+        DevToolsUtils.reportException("evalWithDebugger",
+          Error("The frame actor was not found: " + frameActorID));
+      }
+    }
+
+    // If we've been given a frame actor in whose scope we should evaluate the
+    // expression, be sure to use that frame's Debugger (that is, the JavaScript
+    // debugger's Debugger) for the whole operation, not the console's Debugger.
+    // (One Debugger will treat a different Debugger's Debugger.Object instances
+    // as ordinary objects, not as references to be followed, so mixing
+    // debuggers causes strange behaviors.)
+    let dbg = frame ? frameActor.threadActor.dbg : this.dbg;
+
+    return { frame, dbg };
+  },
+
+  /**
    * Evaluates a string using the debugger API.
    *
    * To allow the variables view to update properties from the Web Console we
    * provide the "bindObjectActor" mechanism: the Web Console tells the
    * ObjectActor ID for which it desires to evaluate an expression. The
    * Debugger.Object pointed at by the actor ID is bound such that it is
    * available during expression evaluation (executeInGlobalWithBindings()).
    *
@@ -1207,36 +1249,17 @@ WebConsoleActor.prototype =
       aString = "help()";
     }
 
     // Add easter egg for console.mihai().
     if (trimmedString == "console.mihai()" || trimmedString == "console.mihai();") {
       aString = "\"http://incompleteness.me/blog/2015/02/09/console-dot-mihai/\"";
     }
 
-    // Find the Debugger.Frame of the given FrameActor.
-    let frame = null, frameActor = null;
-    if (aOptions.frameActor) {
-      frameActor = this.conn.getActor(aOptions.frameActor);
-      if (frameActor) {
-        frame = frameActor.frame;
-      }
-      else {
-        DevToolsUtils.reportException("evalWithDebugger",
-          Error("The frame actor was not found: " + aOptions.frameActor));
-      }
-    }
-
-    // If we've been given a frame actor in whose scope we should evaluate the
-    // expression, be sure to use that frame's Debugger (that is, the JavaScript
-    // debugger's Debugger) for the whole operation, not the console's Debugger.
-    // (One Debugger will treat a different Debugger's Debugger.Object instances
-    // as ordinary objects, not as references to be followed, so mixing
-    // debuggers causes strange behaviors.)
-    let dbg = frame ? frameActor.threadActor.dbg : this.dbg;
+    let { frame, dbg } = this.getEvalDebuggerInfo(aOptions.frameActor);
     let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow);
 
     // If we have an object to bind to |_self|, create a Debugger.Object
     // referring to that object, belonging to dbg.
     let bindSelf = null;
     if (aOptions.bindObjectActor || aOptions.selectedObjectActor) {
       let objActor = this.getActorByID(aOptions.bindObjectActor ||
                                        aOptions.selectedObjectActor);