Bug 1224073 - Use the Debugger.Object API to call Error.toString directly. r=bgrins
authorEddy Bruel <ejpbruel@mozilla.com
Wed, 21 Sep 2016 15:31:40 +0200
changeset 359929 c5be9378df21792f04243607db61f2d774f2d870
parent 359928 1126d2d006dc3e0f323fb2f09df23be1b688b78a
child 359930 e1d144423657aa6b1def32f6f0c6825d8741a429
push id1369
push userjlorenzo@mozilla.com
push dateMon, 27 Feb 2017 14:59:41 +0000
treeherdermozilla-release@d75a1dba431f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1224073
milestone52.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 1224073 - Use the Debugger.Object API to call Error.toString directly. r=bgrins
devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js
devtools/server/actors/webconsole.js
devtools/shared/DevToolsUtils.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,19 +38,18 @@ 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"),
-      "command3 executed successfully");
+  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);
   yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -893,27 +893,46 @@ WebConsoleActor.prototype =
     let result, errorDocURL, errorMessage, errorGrip = null;
     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);
 
-          // It is possible that we won't have permission to unwrap an
-          // object and retrieve its errorMessageName.
+        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
+            // as it is seen by the debuggee, so this behavior is correct.
+            //
+            // Unfortunately, we have at least one test that assumes calling the
+            // "toString" property of an error object will succeed if the
+            // debugger is allowed to access it, regardless of whether the
+            // debuggee is allowed to access it or not.
+            //
+            // To accomodate these tests, if calling the "toString" property
+            // from the debuggee compartment fails, we rewrap the error object
+            // in the debugger's compartment, and then call the "toString"
+            // property from there.
+            if (typeof error.unsafeDereference === "function") {
+              errorMessage = error.unsafeDereference().toString();
+            }
+          }
+        }
+
+        // It is possible that we won't have permission to unwrap an
+        // object and retrieve its errorMessageName.
         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.
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -631,8 +631,48 @@ function errorOnFlag(exports, name) {
       throw new Error(msg);
     }
   });
 }
 
 errorOnFlag(exports, "testing");
 errorOnFlag(exports, "wantLogging");
 errorOnFlag(exports, "wantVerbose");
+
+// Calls the property with the given `name` on the given `object`, where
+// `name` is a string, and `object` a Debugger.Object instance.
+///
+// This function uses only the Debugger.Object API to call the property. It
+// avoids the use of unsafeDeference. This is useful for example in workers,
+// where unsafeDereference will return an opaque security wrapper to the
+// referent.
+function callPropertyOnObject(object, name) {
+  // Find the property.
+  let descriptor;
+  let proto = object;
+  do {
+    descriptor = proto.getOwnPropertyDescriptor(name);
+    if (descriptor !== undefined) {
+      break;
+    }
+    proto = proto.proto;
+  } while (proto !== null);
+  if (descriptor === undefined) {
+    throw new Error("No such property");
+  }
+  let value = descriptor.value;
+  if (typeof value !== "object" || value === null || !("callable" in value)) {
+    throw new Error("Not a callable object.");
+  }
+
+  // Call the property.
+  let result = value.call(object);
+  if (result === null) {
+    throw new Error("Code was terminated.");
+  }
+  if ("throw" in result) {
+    throw result.throw;
+  }
+  return result.return;
+}
+
+
+exports.callPropertyOnObject = callPropertyOnObject;