Bug 1588839 - Part 3: Preserve cross-process async stacks in sendQuery/sendAsyncMessage calls. r=bzbarsky
authorKris Maglione <maglione.k@gmail.com>
Sat, 07 Dec 2019 18:59:23 +0000
changeset 568106 aa4e0207b45fbab40ab0d310b67630c6aced6859
parent 568105 c51a9a372a814900562f90376156528329d946c2
child 568107 8d30b15ca8f932b30ffdf55910fe6e9e2036ca73
push id12493
push userffxbld-merge
push dateMon, 06 Jan 2020 15:38:57 +0000
treeherdermozilla-beta@63ae456b848d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1588839
milestone73.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 1588839 - Part 3: Preserve cross-process async stacks in sendQuery/sendAsyncMessage calls. r=bzbarsky This makes it much simpler to track down the source of message handler errors by linking the cross-process caller chains which sent the problematic message. Differential Revision: https://phabricator.services.mozilla.com/D50883
dom/ipc/JSWindowActor.cpp
dom/ipc/JSWindowActor.h
dom/ipc/JSWindowActorChild.cpp
dom/ipc/JSWindowActorChild.h
dom/ipc/JSWindowActorParent.cpp
dom/ipc/JSWindowActorParent.h
dom/ipc/PWindowGlobal.ipdl
dom/ipc/WindowGlobalChild.cpp
dom/ipc/WindowGlobalChild.h
dom/ipc/WindowGlobalParent.cpp
dom/ipc/WindowGlobalParent.h
dom/ipc/tests/JSWindowActor/browser_sendQuery.js
--- a/dom/ipc/JSWindowActor.cpp
+++ b/dom/ipc/JSWindowActor.cpp
@@ -107,33 +107,61 @@ void JSWindowActor::RejectPendingQueries
   }
 }
 
 void JSWindowActor::SetName(const nsAString& aName) {
   MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!");
   mName = aName;
 }
 
+static ipc::StructuredCloneData CloneJSStack(JSContext* aCx,
+                                             JS::Handle<JSObject*> aStack) {
+  JS::Rooted<JS::Value> stackVal(aCx, JS::ObjectOrNullValue(aStack));
+
+  {
+    IgnoredErrorResult rv;
+    ipc::StructuredCloneData data;
+    data.Write(aCx, stackVal, rv);
+    if (!rv.Failed()) {
+      return data;
+    }
+  }
+  ErrorResult rv;
+  ipc::StructuredCloneData data;
+  data.Write(aCx, JS::NullHandleValue, rv);
+  return data;
+}
+
+static ipc::StructuredCloneData CaptureJSStack(JSContext* aCx) {
+  JS::Rooted<JSObject*> stack(aCx, nullptr);
+  if (JS::ContextOptionsRef(aCx).asyncStack() &&
+      !JS::CaptureCurrentStack(aCx, &stack)) {
+    JS_ClearPendingException(aCx);
+  }
+
+  return CloneJSStack(aCx, stack);
+}
+
 void JSWindowActor::SendAsyncMessage(JSContext* aCx,
                                      const nsAString& aMessageName,
                                      JS::Handle<JS::Value> aObj,
                                      ErrorResult& aRv) {
   ipc::StructuredCloneData data;
   if (!nsFrameMessageManager::GetParamsForMessage(
           aCx, aObj, JS::UndefinedHandleValue, data)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
   JSWindowActorMessageMeta meta;
   meta.actorName() = mName;
   meta.messageName() = aMessageName;
   meta.kind() = JSWindowActorMessageKind::Message;
 
-  SendRawMessage(meta, std::move(data), aRv);
+  SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
 }
 
 already_AddRefed<Promise> JSWindowActor::SendQuery(
     JSContext* aCx, const nsAString& aMessageName, JS::Handle<JS::Value> aObj,
     ErrorResult& aRv) {
   ipc::StructuredCloneData data;
   if (!nsFrameMessageManager::GetParamsForMessage(
           aCx, aObj, JS::UndefinedHandleValue, data)) {
@@ -155,39 +183,60 @@ already_AddRefed<Promise> JSWindowActor:
   JSWindowActorMessageMeta meta;
   meta.actorName() = mName;
   meta.messageName() = aMessageName;
   meta.queryId() = mNextQueryId++;
   meta.kind() = JSWindowActorMessageKind::Query;
 
   mPendingQueries.Put(meta.queryId(), promise);
 
-  SendRawMessage(meta, std::move(data), aRv);
+  SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
   return promise.forget();
 }
 
+#define CHILD_DIAGNOSTIC_ASSERT(test, msg) \
+  do {                                     \
+    if (XRE_IsParentProcess()) {           \
+      MOZ_ASSERT(test, msg);               \
+    } else {                               \
+      MOZ_DIAGNOSTIC_ASSERT(test, msg);    \
+    }                                      \
+  } while (0)
+
 void JSWindowActor::ReceiveRawMessage(const JSWindowActorMessageMeta& aMetadata,
-                                      ipc::StructuredCloneData&& aData) {
+                                      ipc::StructuredCloneData&& aData,
+                                      ipc::StructuredCloneData&& aStack) {
   AutoEntryScript aes(GetParentObject(), "JSWindowActor message handler");
   JSContext* cx = aes.cx();
 
   // Read the message into a JS object from IPC.
   ErrorResult error;
   JS::Rooted<JS::Value> data(cx);
   aData.Read(cx, &data, error);
   if (NS_WARN_IF(error.Failed())) {
-    if (XRE_IsParentProcess()) {
-      MOZ_ASSERT(false, "Should not receive non-decodable data");
-    } else {
-      MOZ_DIAGNOSTIC_ASSERT(false, "Should not receive non-decodable data");
-    }
+    CHILD_DIAGNOSTIC_ASSERT(false, "Should not receive non-decodable data");
     MOZ_ALWAYS_TRUE(error.MaybeSetPendingException(cx));
     return;
   }
 
+  JS::Rooted<JSObject*> stack(cx);
+  Maybe<JS::AutoSetAsyncStackForNewCalls> stackSetter;
+  {
+    JS::Rooted<JS::Value> stackVal(cx);
+    aStack.Read(cx, &stackVal, IgnoreErrors());
+    if (stackVal.isObject()) {
+      stack = &stackVal.toObject();
+      if (!js::IsSavedFrame(stack)) {
+        CHILD_DIAGNOSTIC_ASSERT(false, "Stack must be a SavedFrame object");
+        return;
+      }
+      stackSetter.emplace(cx, stack, "JSWindowActor query");
+    }
+  }
+
   switch (aMetadata.kind()) {
     case JSWindowActorMessageKind::QueryResolve:
     case JSWindowActorMessageKind::QueryReject:
       ReceiveQueryReply(cx, aMetadata, data, error);
       break;
 
     case JSWindowActorMessageKind::Message:
     case JSWindowActorMessageKind::Query:
@@ -223,17 +272,17 @@ void JSWindowActor::ReceiveMessageOrQuer
   // listener on this promise will then send the reply.
   RefPtr<Promise> promise;
   if (aMetadata.kind() == JSWindowActorMessageKind::Query) {
     promise = Promise::Create(xpc::NativeGlobal(global), aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
 
-    RefPtr<QueryHandler> handler = new QueryHandler(this, aMetadata);
+    RefPtr<QueryHandler> handler = new QueryHandler(this, aMetadata, promise);
     promise->AppendNativeHandler(handler);
   }
 
   // Invoke the actual callback.
   JS::Rooted<JS::Value> retval(aCx);
   RefPtr<MessageListener> messageListener =
       new MessageListener(self, global, nullptr, nullptr);
   messageListener->ReceiveMessage(argument, &retval, aRv,
@@ -277,18 +326,20 @@ void JSWindowActor::ReceiveQueryReply(JS
   } else {
     promise->MaybeReject(data);
   }
 }
 
 // Native handler for our generated promise which is used to handle Queries and
 // send the reply when their promises have been resolved.
 JSWindowActor::QueryHandler::QueryHandler(
-    JSWindowActor* aActor, const JSWindowActorMessageMeta& aMetadata)
+    JSWindowActor* aActor, const JSWindowActorMessageMeta& aMetadata,
+    Promise* aPromise)
     : mActor(aActor),
+      mPromise(aPromise),
       mMessageName(aMetadata.messageName()),
       mQueryId(aMetadata.queryId()) {}
 
 void JSWindowActor::QueryHandler::RejectedCallback(
     JSContext* aCx, JS::Handle<JS::Value> aValue) {
   if (!mActor) {
     // Make sure that this rejection is reported. See comment below.
     Unused << JS::CallOriginalPromiseReject(aCx, aValue);
@@ -372,23 +423,28 @@ void JSWindowActor::QueryHandler::SendRe
   MOZ_ASSERT(mActor);
 
   JSWindowActorMessageMeta meta;
   meta.actorName() = mActor->Name();
   meta.messageName() = mMessageName;
   meta.queryId() = mQueryId;
   meta.kind() = aKind;
 
-  mActor->SendRawMessage(meta, std::move(aData), IgnoreErrors());
+  JS::Rooted<JSObject*> promise(aCx, mPromise->PromiseObj());
+  JS::Rooted<JSObject*> stack(aCx, JS::GetPromiseResolutionSite(promise));
+
+  mActor->SendRawMessage(meta, std::move(aData), CloneJSStack(aCx, stack),
+                         IgnoreErrors());
   mActor = nullptr;
+  mPromise = nullptr;
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActor::QueryHandler)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActor::QueryHandler)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActor::QueryHandler)
 
-NS_IMPL_CYCLE_COLLECTION(JSWindowActor::QueryHandler, mActor)
+NS_IMPL_CYCLE_COLLECTION(JSWindowActor::QueryHandler, mActor, mPromise)
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/JSWindowActor.h
+++ b/dom/ipc/JSWindowActor.h
@@ -48,28 +48,30 @@ class JSWindowActor : public nsISupports
                         JS::Handle<JS::Value> aObj, ErrorResult& aRv);
 
   already_AddRefed<Promise> SendQuery(JSContext* aCx,
                                       const nsAString& aMessageName,
                                       JS::Handle<JS::Value> aObj,
                                       ErrorResult& aRv);
 
   void ReceiveRawMessage(const JSWindowActorMessageMeta& aMetadata,
-                         ipc::StructuredCloneData&& aData);
+                         ipc::StructuredCloneData&& aData,
+                         ipc::StructuredCloneData&& aStack);
 
   virtual nsIGlobalObject* GetParentObject() const = 0;
 
   void RejectPendingQueries();
 
  protected:
   // Send the message described by the structured clone data |aData|, and the
   // message metadata |aMetadata|. The underlying transport should call the
   // |ReceiveMessage| method on the other side asynchronously.
   virtual void SendRawMessage(const JSWindowActorMessageMeta& aMetadata,
                               ipc::StructuredCloneData&& aData,
+                              ipc::StructuredCloneData&& aStack,
                               ErrorResult& aRv) = 0;
 
   virtual ~JSWindowActor() = default;
 
   void SetName(const nsAString& aName);
 
   void StartDestroy();
 
@@ -93,31 +95,32 @@ class JSWindowActor : public nsISupports
   // Helper object used while processing query messages to send the final reply
   // message.
   class QueryHandler final : public PromiseNativeHandler {
    public:
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     NS_DECL_CYCLE_COLLECTION_CLASS(QueryHandler)
 
     QueryHandler(JSWindowActor* aActor,
-                 const JSWindowActorMessageMeta& aMetadata);
+                 const JSWindowActorMessageMeta& aMetadata, Promise* aPromise);
 
     void RejectedCallback(JSContext* aCx,
                           JS::Handle<JS::Value> aValue) override;
 
     void ResolvedCallback(JSContext* aCx,
                           JS::Handle<JS::Value> aValue) override;
 
    private:
     ~QueryHandler() = default;
 
     void SendReply(JSContext* aCx, JSWindowActorMessageKind aKind,
                    ipc::StructuredCloneData&& aData);
 
     RefPtr<JSWindowActor> mActor;
+    RefPtr<Promise> mPromise;
     nsString mMessageName;
     uint64_t mQueryId;
   };
 
   nsCOMPtr<nsISupports> mWrappedJS;
   nsString mName;
   nsRefPtrHashtable<nsUint64HashKey, Promise> mPendingQueries;
   uint64_t mNextQueryId;
--- a/dom/ipc/JSWindowActorChild.cpp
+++ b/dom/ipc/JSWindowActorChild.cpp
@@ -40,63 +40,69 @@ void JSWindowActorChild::Init(const nsAS
 }
 
 namespace {
 
 class AsyncMessageToParent : public Runnable {
  public:
   AsyncMessageToParent(const JSWindowActorMessageMeta& aMetadata,
                        ipc::StructuredCloneData&& aData,
+                       ipc::StructuredCloneData&& aStack,
                        WindowGlobalChild* aManager)
       : mozilla::Runnable("WindowGlobalParent::HandleAsyncMessage"),
         mMetadata(aMetadata),
         mData(std::move(aData)),
+        mStack(std::move(aStack)),
         mManager(aManager) {}
 
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread.");
     RefPtr<WindowGlobalParent> parent = mManager->GetParentActor();
     if (parent) {
-      parent->ReceiveRawMessage(mMetadata, std::move(mData));
+      parent->ReceiveRawMessage(mMetadata, std::move(mData), std::move(mStack));
     }
     return NS_OK;
   }
 
  private:
   JSWindowActorMessageMeta mMetadata;
   ipc::StructuredCloneData mData;
+  ipc::StructuredCloneData mStack;
   RefPtr<WindowGlobalChild> mManager;
 };
 
 }  // anonymous namespace
 
 void JSWindowActorChild::SendRawMessage(const JSWindowActorMessageMeta& aMeta,
                                         ipc::StructuredCloneData&& aData,
+                                        ipc::StructuredCloneData&& aStack,
                                         ErrorResult& aRv) {
   if (NS_WARN_IF(!mCanSend || !mManager || !mManager->CanSend())) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (mManager->IsInProcess()) {
-    nsCOMPtr<nsIRunnable> runnable =
-        new AsyncMessageToParent(aMeta, std::move(aData), mManager);
+    nsCOMPtr<nsIRunnable> runnable = new AsyncMessageToParent(
+        aMeta, std::move(aData), std::move(aStack), mManager);
     NS_DispatchToMainThread(runnable.forget());
     return;
   }
 
   // Cross-process case - send data over WindowGlobalChild to other side.
   ClonedMessageData msgData;
+  ClonedMessageData stackData;
   ContentChild* cc = ContentChild::GetSingleton();
-  if (NS_WARN_IF(!aData.BuildClonedMessageDataForChild(cc, msgData))) {
+  if (NS_WARN_IF(!aData.BuildClonedMessageDataForChild(cc, msgData)) ||
+      NS_WARN_IF(!aStack.BuildClonedMessageDataForChild(cc, stackData))) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData))) {
+  if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData, stackData))) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 }
 
 Document* JSWindowActorChild::GetDocument(ErrorResult& aRv) {
   if (!mManager) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
--- a/dom/ipc/JSWindowActorChild.h
+++ b/dom/ipc/JSWindowActorChild.h
@@ -58,16 +58,17 @@ class JSWindowActorChild final : public 
   Document* GetDocument(ErrorResult& aRv);
   BrowsingContext* GetBrowsingContext(ErrorResult& aRv);
   nsIDocShell* GetDocShell(ErrorResult& aRv);
   Nullable<WindowProxyHolder> GetContentWindow(ErrorResult& aRv);
 
  protected:
   void SendRawMessage(const JSWindowActorMessageMeta& aMeta,
                       ipc::StructuredCloneData&& aData,
+                      ipc::StructuredCloneData&& aStack,
                       ErrorResult& aRv) override;
 
  private:
   ~JSWindowActorChild();
 
   bool mCanSend = true;
   RefPtr<WindowGlobalChild> mManager;
 
--- a/dom/ipc/JSWindowActorParent.cpp
+++ b/dom/ipc/JSWindowActorParent.cpp
@@ -33,64 +33,70 @@ void JSWindowActorParent::Init(const nsA
 }
 
 namespace {
 
 class AsyncMessageToChild : public Runnable {
  public:
   AsyncMessageToChild(const JSWindowActorMessageMeta& aMetadata,
                       ipc::StructuredCloneData&& aData,
+                      ipc::StructuredCloneData&& aStack,
                       WindowGlobalParent* aManager)
       : mozilla::Runnable("WindowGlobalChild::HandleAsyncMessage"),
         mMetadata(aMetadata),
         mData(std::move(aData)),
+        mStack(std::move(aStack)),
         mManager(aManager) {}
 
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread.");
     RefPtr<WindowGlobalChild> child = mManager->GetChildActor();
     if (child) {
-      child->ReceiveRawMessage(mMetadata, std::move(mData));
+      child->ReceiveRawMessage(mMetadata, std::move(mData), std::move(mStack));
     }
     return NS_OK;
   }
 
  private:
   JSWindowActorMessageMeta mMetadata;
   ipc::StructuredCloneData mData;
+  ipc::StructuredCloneData mStack;
   RefPtr<WindowGlobalParent> mManager;
 };
 
 }  // anonymous namespace
 
 void JSWindowActorParent::SendRawMessage(const JSWindowActorMessageMeta& aMeta,
                                          ipc::StructuredCloneData&& aData,
+                                         ipc::StructuredCloneData&& aStack,
                                          ErrorResult& aRv) {
   if (NS_WARN_IF(!mCanSend || !mManager || !mManager->CanSend())) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (mManager->IsInProcess()) {
-    nsCOMPtr<nsIRunnable> runnable =
-        new AsyncMessageToChild(aMeta, std::move(aData), mManager);
+    nsCOMPtr<nsIRunnable> runnable = new AsyncMessageToChild(
+        aMeta, std::move(aData), std::move(aStack), mManager);
     NS_DispatchToMainThread(runnable.forget());
     return;
   }
 
   // Cross-process case - send data over WindowGlobalParent to other side.
   ClonedMessageData msgData;
+  ClonedMessageData stackData;
   RefPtr<BrowserParent> browserParent = mManager->GetBrowserParent();
   ContentParent* cp = browserParent->Manager();
-  if (NS_WARN_IF(!aData.BuildClonedMessageDataForParent(cp, msgData))) {
+  if (NS_WARN_IF(!aData.BuildClonedMessageDataForParent(cp, msgData)) ||
+      NS_WARN_IF(!aStack.BuildClonedMessageDataForParent(cp, stackData))) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData))) {
+  if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData, stackData))) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 }
 
 CanonicalBrowsingContext* JSWindowActorParent::GetBrowsingContext(
     ErrorResult& aRv) {
   if (!mManager) {
--- a/dom/ipc/JSWindowActorParent.h
+++ b/dom/ipc/JSWindowActorParent.h
@@ -46,16 +46,17 @@ class JSWindowActorParent final : public
   void Init(const nsAString& aName, WindowGlobalParent* aManager);
   void StartDestroy();
   void AfterDestroy();
   CanonicalBrowsingContext* GetBrowsingContext(ErrorResult& aRv);
 
  protected:
   void SendRawMessage(const JSWindowActorMessageMeta& aMeta,
                       ipc::StructuredCloneData&& aData,
+                      ipc::StructuredCloneData&& aStack,
                       ErrorResult& aRv) override;
 
  private:
   ~JSWindowActorParent();
 
   bool mCanSend = true;
   RefPtr<WindowGlobalParent> mManager;
 };
--- a/dom/ipc/PWindowGlobal.ipdl
+++ b/dom/ipc/PWindowGlobal.ipdl
@@ -63,17 +63,17 @@ child:
 
   async LoadURIInChild(nsDocShellLoadState aLoadState, bool aSetNavigating);
 
   async InternalLoadInChild(nsDocShellLoadState aLoadState, bool aTakeFocus);
 
   async DisplayLoadError(nsString aURI);
 
 both:
-  async RawMessage(JSWindowActorMessageMeta aMetadata, ClonedMessageData aData);
+  async RawMessage(JSWindowActorMessageMeta aMetadata, ClonedMessageData aData, ClonedMessageData aStack);
 
 parent:
   // Load the given URI load state into the current owner process of the given
   // BrowsingContext. aTargetBC must be in the same BrowsingContextGroup as this
   // window global.
   async LoadURI(BrowsingContext aTargetBC, nsDocShellLoadState aLoadState, bool aSetNavigating);
 
   async InternalLoad(BrowsingContext aTargetBC, nsDocShellLoadState aLoadState);
--- a/dom/ipc/WindowGlobalChild.cpp
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -421,29 +421,33 @@ mozilla::ipc::IPCResult WindowGlobalChil
     }
   }
 
   aResolve(result);
   return IPC_OK();
 }
 
 IPCResult WindowGlobalChild::RecvRawMessage(
-    const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData) {
+    const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData,
+    const ClonedMessageData& aStack) {
   StructuredCloneData data;
   data.BorrowFromClonedMessageDataForChild(aData);
-  ReceiveRawMessage(aMeta, std::move(data));
+  StructuredCloneData stack;
+  stack.BorrowFromClonedMessageDataForChild(aStack);
+  ReceiveRawMessage(aMeta, std::move(data), std::move(stack));
   return IPC_OK();
 }
 
 void WindowGlobalChild::ReceiveRawMessage(const JSWindowActorMessageMeta& aMeta,
-                                          StructuredCloneData&& aData) {
+                                          StructuredCloneData&& aData,
+                                          StructuredCloneData&& aStack) {
   RefPtr<JSWindowActorChild> actor =
       GetActor(aMeta.actorName(), IgnoreErrors());
   if (actor) {
-    actor->ReceiveRawMessage(aMeta, std::move(aData));
+    actor->ReceiveRawMessage(aMeta, std::move(aData), std::move(aStack));
   }
 }
 
 void WindowGlobalChild::SetDocumentURI(nsIURI* aDocumentURI) {
 #ifdef MOZ_GECKO_PROFILER
   // Registers a DOM Window with the profiler. It re-registers the same Inner
   // Window ID with different URIs because when a Browsing context is first
   // loaded, the first url loaded in it will be about:blank. This call keeps the
--- a/dom/ipc/WindowGlobalChild.h
+++ b/dom/ipc/WindowGlobalChild.h
@@ -80,17 +80,18 @@ class WindowGlobalChild final : public W
   // |nullptr| if the actor has been torn down, or is not in-process.
   already_AddRefed<WindowGlobalParent> GetParentActor();
 
   // Get this actor's manager if it is not an in-process actor. Returns
   // |nullptr| if the actor has been torn down, or is in-process.
   already_AddRefed<BrowserChild> GetBrowserChild();
 
   void ReceiveRawMessage(const JSWindowActorMessageMeta& aMeta,
-                         ipc::StructuredCloneData&& aData);
+                         ipc::StructuredCloneData&& aData,
+                         ipc::StructuredCloneData&& aStack);
 
   // Get a JS actor object by name.
   already_AddRefed<JSWindowActorChild> GetActor(const nsAString& aName,
                                                 ErrorResult& aRv);
 
   // Create and initialize the WindowGlobalChild object.
   static already_AddRefed<WindowGlobalChild> Create(
       nsGlobalWindowInner* aWindow);
@@ -107,17 +108,18 @@ class WindowGlobalChild final : public W
                        JS::Handle<JSObject*> aGivenProto) override;
 
  protected:
   const nsAString& GetRemoteType() override;
   JSWindowActor::Type GetSide() override { return JSWindowActor::Type::Child; }
 
   // IPC messages
   mozilla::ipc::IPCResult RecvRawMessage(const JSWindowActorMessageMeta& aMeta,
-                                         const ClonedMessageData& aData);
+                                         const ClonedMessageData& aData,
+                                         const ClonedMessageData& aStack);
 
   mozilla::ipc::IPCResult RecvLoadURIInChild(nsDocShellLoadState* aLoadState,
                                              bool aSetNavigating);
 
   mozilla::ipc::IPCResult RecvInternalLoadInChild(
       nsDocShellLoadState* aLoadState, bool aTakeFocus);
 
   mozilla::ipc::IPCResult RecvDisplayLoadError(const nsAString& aURI);
--- a/dom/ipc/WindowGlobalParent.cpp
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -275,29 +275,33 @@ IPCResult WindowGlobalParent::RecvDestro
       }
       Unused << Send__delete__(this);
     }
   }
   return IPC_OK();
 }
 
 IPCResult WindowGlobalParent::RecvRawMessage(
-    const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData) {
+    const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData,
+    const ClonedMessageData& aStack) {
   StructuredCloneData data;
   data.BorrowFromClonedMessageDataForParent(aData);
-  ReceiveRawMessage(aMeta, std::move(data));
+  StructuredCloneData stack;
+  stack.BorrowFromClonedMessageDataForParent(aStack);
+  ReceiveRawMessage(aMeta, std::move(data), std::move(stack));
   return IPC_OK();
 }
 
 void WindowGlobalParent::ReceiveRawMessage(
-    const JSWindowActorMessageMeta& aMeta, StructuredCloneData&& aData) {
+    const JSWindowActorMessageMeta& aMeta, StructuredCloneData&& aData,
+    StructuredCloneData&& aStack) {
   RefPtr<JSWindowActorParent> actor =
       GetActor(aMeta.actorName(), IgnoreErrors());
   if (actor) {
-    actor->ReceiveRawMessage(aMeta, std::move(aData));
+    actor->ReceiveRawMessage(aMeta, std::move(aData), std::move(aStack));
   }
 }
 
 const nsAString& WindowGlobalParent::GetRemoteType() {
   if (RefPtr<BrowserParent> browserParent = GetBrowserParent()) {
     return browserParent->Manager()->GetRemoteType();
   }
 
--- a/dom/ipc/WindowGlobalParent.h
+++ b/dom/ipc/WindowGlobalParent.h
@@ -69,17 +69,18 @@ class WindowGlobalParent final : public 
   already_AddRefed<JSWindowActorParent> GetActor(const nsAString& aName,
                                                  ErrorResult& aRv);
 
   // Get this actor's manager if it is not an in-process actor. Returns
   // |nullptr| if the actor has been torn down, or is in-process.
   already_AddRefed<BrowserParent> GetBrowserParent();
 
   void ReceiveRawMessage(const JSWindowActorMessageMeta& aMeta,
-                         ipc::StructuredCloneData&& aData);
+                         ipc::StructuredCloneData&& aData,
+                         ipc::StructuredCloneData&& aStack);
 
   // The principal of this WindowGlobal. This value will not change over the
   // lifetime of the WindowGlobal object, even to reflect changes in
   // |document.domain|.
   nsIPrincipal* DocumentPrincipal() { return mDocumentPrincipal; }
 
   // The BrowsingContext which this WindowGlobal has been loaded into.
   CanonicalBrowsingContext* BrowsingContext() override {
@@ -143,17 +144,18 @@ class WindowGlobalParent final : public 
   mozilla::ipc::IPCResult RecvSetIsInitialDocument(bool aIsInitialDocument) {
     mIsInitialDocument = aIsInitialDocument;
     return IPC_OK();
   }
   mozilla::ipc::IPCResult RecvSetHasBeforeUnload(bool aHasBeforeUnload);
   mozilla::ipc::IPCResult RecvBecomeCurrentWindowGlobal();
   mozilla::ipc::IPCResult RecvDestroy();
   mozilla::ipc::IPCResult RecvRawMessage(const JSWindowActorMessageMeta& aMeta,
-                                         const ClonedMessageData& aData);
+                                         const ClonedMessageData& aData,
+                                         const ClonedMessageData& aStack);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void DrawSnapshotInternal(gfx::CrossProcessPaint* aPaint,
                             const Maybe<IntRect>& aRect, float aScale,
                             nscolor aBackgroundColor, uint32_t aFlags);
 
   // WebShare API - try to share
--- a/dom/ipc/tests/JSWindowActor/browser_sendQuery.js
+++ b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js
@@ -1,52 +1,71 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+function maybeAsyncStack(offset, column) {
+  if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) {
+    return "";
+  }
+
+  let stack = Error().stack.replace(/^.*?\n/, "");
+  return (
+    "JSWindowActor query*" +
+    stack.replace(
+      /^([^\n]+?):(\d+):\d+/,
+      (m0, m1, m2) => `${m1}:${+m2 + offset}:${column}`
+    )
+  );
+}
+
 declTest("sendQuery Error", {
   async test(browser) {
     let parent = browser.browsingContext.currentWindowGlobal;
     let actorParent = parent.getActor("Test");
 
+    let asyncStack = maybeAsyncStack(2, 8);
     let error = await actorParent
       .sendQuery("error", { message: "foo" })
       .catch(e => e);
 
     is(error.message, "foo", "Error should have the correct message");
     is(error.name, "SyntaxError", "Error should have the correct name");
     is(
       error.stack,
-      "receiveMessage@resource://testing-common/TestChild.jsm:28:31\n",
+      "receiveMessage@resource://testing-common/TestChild.jsm:28:31\n" +
+        asyncStack,
       "Error should have the correct stack"
     );
   },
 });
 
 declTest("sendQuery Exception", {
   async test(browser) {
     let parent = browser.browsingContext.currentWindowGlobal;
     let actorParent = parent.getActor("Test");
 
+    let asyncStack = maybeAsyncStack(2, 8);
     let error = await actorParent
       .sendQuery("exception", {
         message: "foo",
         result: Cr.NS_ERROR_INVALID_ARG,
       })
       .catch(e => e);
 
     is(error.message, "foo", "Error should have the correct message");
     is(
       error.result,
       Cr.NS_ERROR_INVALID_ARG,
       "Error should have the correct result code"
     );
     is(
       error.stack,
-      "receiveMessage@resource://testing-common/TestChild.jsm:31:22\n",
+      "receiveMessage@resource://testing-common/TestChild.jsm:31:22\n" +
+        asyncStack,
       "Error should have the correct stack"
     );
   },
 });
 
 declTest("sendQuery testing", {
   async test(browser) {
     let parent = browser.browsingContext.currentWindowGlobal;