Bug 1140435 - Part 2 - Allow JS code to provide an async stack when calling a function. r=bz
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Sat, 07 Mar 2015 13:30:34 +0000
changeset 232415 bdacb3e22b546a7879ba00c6700092da28901d8e
parent 232414 750120b03c83b692dc927b0ac3759724ab803cf2
child 232416 fecf1afb083053ea25d5f10c4a03b7080c888fe9
child 232462 ded43d8dee816ea3058dc7d0804c0d1ace23f4b7
push id28379
push userphilringnalda@gmail.com
push dateSun, 08 Mar 2015 03:39:06 +0000
treeherdermozilla-central@fecf1afb0830 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1140435
milestone39.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 1140435 - Part 2 - Allow JS code to provide an async stack when calling a function. r=bz
dom/bindings/Exceptions.cpp
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js
js/xpconnect/tests/unit/xpcshell.ini
xpcom/base/nsIException.idl
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -306,16 +306,17 @@ public:
 
   NS_IMETHOD GetLanguageName(nsACString& aLanguageName) MOZ_OVERRIDE;
   NS_IMETHOD GetFilename(nsAString& aFilename) MOZ_OVERRIDE;
   NS_IMETHOD GetName(nsAString& aFunction) MOZ_OVERRIDE;
   NS_IMETHOD GetAsyncCause(nsAString& aAsyncCause) MOZ_OVERRIDE;
   NS_IMETHOD GetAsyncCaller(nsIStackFrame** aAsyncCaller) MOZ_OVERRIDE;
   NS_IMETHOD GetCaller(nsIStackFrame** aCaller) MOZ_OVERRIDE;
   NS_IMETHOD GetFormattedStack(nsAString& aStack) MOZ_OVERRIDE;
+  NS_IMETHOD GetNativeSavedFrame(JS::MutableHandle<JS::Value> aSavedFrame) MOZ_OVERRIDE;
 
 protected:
   virtual bool IsJSFrame() const MOZ_OVERRIDE {
     return true;
   }
 
   virtual nsresult GetLineno(int32_t* aLineNo) MOZ_OVERRIDE;
   virtual nsresult GetColNo(int32_t* aColNo) MOZ_OVERRIDE;
@@ -745,16 +746,29 @@ NS_IMETHODIMP JSStackFrame::GetFormatted
 }
 
 NS_IMETHODIMP StackFrame::GetFormattedStack(nsAString& aStack)
 {
   aStack.Truncate();
   return NS_OK;
 }
 
+/* readonly attribute jsval nativeSavedFrame; */
+NS_IMETHODIMP JSStackFrame::GetNativeSavedFrame(JS::MutableHandle<JS::Value> aSavedFrame)
+{
+  aSavedFrame.setObjectOrNull(mStack);
+  return NS_OK;
+}
+
+NS_IMETHODIMP StackFrame::GetNativeSavedFrame(JS::MutableHandle<JS::Value> aSavedFrame)
+{
+  aSavedFrame.setNull();
+  return NS_OK;
+}
+
 /* AUTF8String toString (); */
 NS_IMETHODIMP StackFrame::ToString(nsACString& _retval)
 {
   _retval.Truncate();
 
   const char* frametype = IsJSFrame() ? "JS" : "native";
 
   nsString filename;
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -117,17 +117,17 @@ interface nsIXPCComponents_utils_Sandbox
 interface ScheduledGCCallback : nsISupports
 {
     void callback();
 };
 
 /**
 * interface of Components.utils
 */
-[scriptable, uuid(2617a800-63c1-11e4-9803-0800200c9a66)]
+[scriptable, uuid(0354f8b4-08c6-4074-a466-2b6524b64ca3)]
 interface nsIXPCComponents_Utils : nsISupports
 {
 
     /* reportError is designed to be called from JavaScript only.
      *
      * It will report a JS Error object to the JS console, and return. It
      * is meant for use in exception handler blocks which want to "eat"
      * an exception, but still want to report it to the console.
@@ -359,16 +359,30 @@ interface nsIXPCComponents_Utils : nsISu
     jsval nondeterministicGetWeakMapKeys(in jsval aMap);
 
     [implicit_jscontext]
     jsval getJSTestingFunctions();
 
     /*
      * To be called from JS only.
      *
+     * Call 'function', using the provided stack as the async stack responsible
+     * for the call, and propagate its return value or the exception it throws.
+     * The function is called with no arguments, and 'this' is 'undefined'.
+     *
+     * The code in the function will see the given stack frame as the
+     * asyncCaller of its own stack frame, instead of the current caller.
+     */
+    [implicit_jscontext]
+    jsval callFunctionWithAsyncStack(in jsval function, in nsIStackFrame stack,
+                                     in AString asyncCause);
+
+    /*
+     * To be called from JS only.
+     *
      * Returns the global object with which the given object is associated.
      *
      * @param obj The JavaScript object whose global is to be gotten.
      * @return the corresponding global.
      */
     [implicit_jscontext]
     jsval getGlobalForObject(in jsval obj);
 
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2896,16 +2896,57 @@ nsXPCComponents_Utils::GetJSTestingFunct
 {
     JSObject *obj = js::GetTestingFunctions(cx);
     if (!obj)
         return NS_ERROR_XPC_JAVASCRIPT_ERROR;
     retval.setObject(*obj);
     return NS_OK;
 }
 
+/* jsval callFunctionWithStack(in jsval function, in nsIStackFrame stack,
+                               in AString asyncCause); */
+NS_IMETHODIMP
+nsXPCComponents_Utils::CallFunctionWithAsyncStack(HandleValue function,
+                                                  nsIStackFrame *stack,
+                                                  const nsAString &asyncCause,
+                                                  JSContext *cx,
+                                                  MutableHandleValue retval)
+{
+    nsresult rv;
+
+    if (!stack || asyncCause.IsEmpty()) {
+        return NS_ERROR_INVALID_ARG;
+    }
+
+    JS::Rooted<JS::Value> asyncStack(cx);
+    rv = stack->GetNativeSavedFrame(&asyncStack);
+    if (NS_FAILED(rv))
+        return rv;
+    if (!asyncStack.isObject()) {
+        JS_ReportError(cx, "Must use a native JavaScript stack frame");
+        return NS_ERROR_INVALID_ARG;
+    }
+
+    JS::Rooted<JSObject*> asyncStackObj(cx, &asyncStack.toObject());
+    JS::Rooted<JSString*> asyncCauseString(cx, JS_NewUCStringCopyN(cx, asyncCause.BeginReading(),
+                                                                       asyncCause.Length()));
+    if (!asyncCauseString)
+        return NS_ERROR_OUT_OF_MEMORY;
+
+    JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, asyncCauseString);
+
+    if (!JS_CallFunctionValue(cx, JS::NullPtr(), function,
+                              JS::HandleValueArray::empty(), retval))
+    {
+        return NS_ERROR_XPC_JAVASCRIPT_ERROR;
+    }
+
+    return NS_OK;
+}
+
 /* void getGlobalForObject(); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::GetGlobalForObject(HandleValue object,
                                           JSContext *cx,
                                           MutableHandleValue retval)
 {
     // First argument must be an object.
     if (object.isPrimitive())
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js
@@ -0,0 +1,23 @@
+function run_test() {
+  function getAsyncStack() {
+    return Components.stack;
+  }
+
+  // asyncCause may contain non-ASCII characters.
+  let testAsyncCause = "Tes" + String.fromCharCode(355) + "String";
+
+  Components.utils.callFunctionWithAsyncStack(function asyncCallback() {
+    let stack = Components.stack;
+
+    do_check_eq(stack.name, "asyncCallback");
+    do_check_eq(stack.caller.name, null);
+    do_check_eq(stack.asyncCause, null);
+
+    do_check_eq(stack.asyncCaller.name, "getAsyncStack");
+    do_check_eq(stack.asyncCaller.asyncCause, testAsyncCause);
+    do_check_eq(stack.asyncCaller.asyncCaller, null);
+
+    do_check_eq(stack.asyncCaller.caller.name, "run_test");
+    do_check_eq(stack.asyncCaller.caller.asyncCause, null);
+  }, getAsyncStack(), testAsyncCause);
+}
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -50,16 +50,17 @@ support-files =
 [test_bug1033253.js]
 [test_bug1033920.js]
 [test_bug1033927.js]
 [test_bug1034262.js]
 [test_bug1082450.js]
 [test_bug1081990.js]
 [test_bug1110546.js]
 [test_bug_442086.js]
+[test_callFunctionWithAsyncStack.js]
 [test_file.js]
 [test_blob.js]
 [test_blob2.js]
 [test_file2.js]
 [test_import.js]
 [test_import_fail.js]
 [test_interposition.js]
 [test_isModuleLoaded.js]
--- a/xpcom/base/nsIException.idl
+++ b/xpcom/base/nsIException.idl
@@ -5,17 +5,17 @@
 
 /*
  * Interfaces for representing cross-language exceptions and stack traces.
  */
 
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(9787bc41-1ec9-4fe0-8b31-ffee5db6a893)]
+[scriptable, uuid(28bfb2a2-5ea6-4738-918b-049dc4d51f0b)]
 interface nsIStackFrame : nsISupports
 {
     // see nsIProgrammingLanguage for list of language consts
     readonly attribute uint32_t                language;
     readonly attribute AUTF8String             languageName;
     readonly attribute AString                 filename;
     readonly attribute AString                 name;
     // Valid line numbers begin at '1'. '0' indicates unknown.
@@ -26,16 +26,20 @@ interface nsIStackFrame : nsISupports
     readonly attribute nsIStackFrame           asyncCaller;
     readonly attribute nsIStackFrame           caller;
 
     // Returns a formatted stack string that looks like the sort of
     // string that would be returned by .stack on JS Error objects.
     // Only works on JS-language stack frames.
     readonly attribute AString                 formattedStack;
 
+    // Returns the underlying SavedFrame object for native JavaScript stacks,
+    // or null if this is not a native JavaScript stack frame.
+    readonly attribute jsval                   nativeSavedFrame;
+
     AUTF8String toString();
 };
 
 [scriptable, uuid(1caf1461-be1d-4b79-a552-5292b6bf3c35)]
 interface nsIException : nsISupports
 {
     // A custom message set by the thrower.
     [binaryname(MessageMoz)] readonly attribute AUTF8String message;