Bug 1544936 - Part 1: Add a sendQuery method to send an async message with a response to JSWindowActor, r=mconley,jdai
authorNika Layzell <nika@thelayzells.com>
Thu, 18 Apr 2019 19:37:15 +0000
changeset 470496 5a351207ac3efe9a69bc3fe53ba988462b17b8a4
parent 470495 8015d85e20f5a3721db7998d7cc3234786eed958
child 470497 176055665c32ad9693c66078192064f476edcf95
push id35906
push useraciure@mozilla.com
push dateTue, 23 Apr 2019 22:14:56 +0000
treeherdermozilla-central@0ce3633f8b80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley, jdai
bugs1544936
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 1544936 - Part 1: Add a sendQuery method to send an async message with a response to JSWindowActor, r=mconley,jdai This adds a single new method, which acts like sendAsyncMessage, but also returns a promise. This promise is fulfilled when the promise returned from the receiveMessage callback is resolved. ``` partial interface JSWindowActor { [Throws] Promise<any> sendQuery(DOMString messageName, optional any obj, optional any transfers); } ``` Differential Revision: https://phabricator.services.mozilla.com/D27809
dom/chrome-webidl/JSWindowActor.webidl
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/moz.build
--- a/dom/chrome-webidl/JSWindowActor.webidl
+++ b/dom/chrome-webidl/JSWindowActor.webidl
@@ -7,16 +7,21 @@
 interface nsISupports;
 
 [NoInterfaceObject]
 interface JSWindowActor {
   [Throws]
   void sendAsyncMessage(DOMString messageName,
                         optional any obj,
                         optional any transfers);
+
+  [Throws]
+  Promise<any> sendQuery(DOMString messageName,
+                         optional any obj,
+                         optional any transfers);
 };
 
 [ChromeOnly, ChromeConstructor]
 interface JSWindowActorParent {
   readonly attribute WindowGlobalParent manager;
 };
 JSWindowActorParent implements JSWindowActor;
 
new file mode 100644
--- /dev/null
+++ b/dom/ipc/JSWindowActor.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/JSWindowActor.h"
+#include "mozilla/dom/JSWindowActorBinding.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/PWindowGlobal.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActor)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActor)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(JSWindowActor)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSWindowActor)
+  tmp->RejectPendingQueries();  // Clear out & reject mPendingQueries
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSWindowActor)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingQueries)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(JSWindowActor)
+
+JSWindowActor::JSWindowActor() : mNextQueryId(0) {}
+
+nsIGlobalObject* JSWindowActor::GetParentObject() const {
+  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+void JSWindowActor::RejectPendingQueries() {
+  // Take our queries out, in case somehow rejecting promises can trigger
+  // additions or removals.
+  nsRefPtrHashtable<nsUint64HashKey, Promise> pendingQueries;
+  mPendingQueries.SwapElements(pendingQueries);
+  for (auto iter = pendingQueries.Iter(); !iter.Done(); iter.Next()) {
+    iter.Data()->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  }
+}
+
+void JSWindowActor::SetName(const nsAString& aName) {
+  MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!");
+  mName = aName;
+}
+
+void JSWindowActor::SendAsyncMessage(JSContext* aCx,
+                                     const nsAString& aMessageName,
+                                     JS::Handle<JS::Value> aObj,
+                                     JS::Handle<JS::Value> aTransfers,
+                                     ErrorResult& aRv) {
+  ipc::StructuredCloneData data;
+  if (!aObj.isUndefined() && !nsFrameMessageManager::GetParamsForMessage(
+                                 aCx, aObj, aTransfers, 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);
+}
+
+already_AddRefed<Promise> JSWindowActor::SendQuery(
+    JSContext* aCx, const nsAString& aMessageName, JS::Handle<JS::Value> aObj,
+    JS::Handle<JS::Value> aTransfers, ErrorResult& aRv) {
+  ipc::StructuredCloneData data;
+  if (!aObj.isUndefined() && !nsFrameMessageManager::GetParamsForMessage(
+                                 aCx, aObj, aTransfers, data)) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+    return nullptr;
+  }
+
+  nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  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);
+  return promise.forget();
+}
+
+void JSWindowActor::ReceiveRawMessage(const JSWindowActorMessageMeta& aMetadata,
+                                      ipc::StructuredCloneData&& aData) {
+  AutoEntryScript aes(xpc::PrivilegedJunkScope(),
+                      "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())) {
+    MOZ_ALWAYS_TRUE(error.MaybeSetPendingException(cx));
+    return;
+  }
+
+  switch (aMetadata.kind()) {
+    case JSWindowActorMessageKind::QueryResolve:
+    case JSWindowActorMessageKind::QueryReject:
+      ReceiveQueryReply(cx, aMetadata, data, error);
+      break;
+
+    case JSWindowActorMessageKind::Message:
+    case JSWindowActorMessageKind::Query:
+      ReceiveMessageOrQuery(cx, aMetadata, data, error);
+      break;
+
+    default:
+      MOZ_ASSERT_UNREACHABLE();
+  }
+
+  if (NS_WARN_IF(error.Failed())) {
+    MOZ_ALWAYS_TRUE(error.MaybeSetPendingException(cx));
+  }
+}
+
+void JSWindowActor::ReceiveMessageOrQuery(
+    JSContext* aCx, const JSWindowActorMessageMeta& aMetadata,
+    JS::Handle<JS::Value> aData, ErrorResult& aRv) {
+  // The argument which we want to pass to IPC.
+  RootedDictionary<ReceiveMessageArgument> argument(aCx);
+  argument.mObjects = JS_NewPlainObject(aCx);
+  argument.mTarget = this;
+  argument.mName = aMetadata.messageName();
+  argument.mData = aData;
+  argument.mJson = aData;
+  argument.mSync = false;
+
+  JS::Rooted<JSObject*> self(aCx, GetWrapper());
+  JS::Rooted<JSObject*> global(aCx, JS::GetNonCCWObjectGlobal(self));
+
+  // We only need to create a promise if we're dealing with a query here. It
+  // will be resolved or rejected once the listener has been called. Our
+  // 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);
+    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,
+                                  "JSWindowActor receive message");
+
+  // If we have a promise, resolve or reject it respectively.
+  if (promise) {
+    if (aRv.Failed()) {
+      promise->MaybeReject(aRv);
+    } else {
+      promise->MaybeResolve(aCx, retval);
+    }
+  }
+}
+
+void JSWindowActor::ReceiveQueryReply(JSContext* aCx,
+                                      const JSWindowActorMessageMeta& aMetadata,
+                                      JS::Handle<JS::Value> aData,
+                                      ErrorResult& aRv) {
+  if (NS_WARN_IF(aMetadata.actorName() != mName)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  RefPtr<Promise> promise;
+  if (NS_WARN_IF(!mPendingQueries.Remove(aMetadata.queryId(),
+                                         getter_AddRefs(promise)))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  if (aMetadata.kind() == JSWindowActorMessageKind::QueryResolve) {
+    promise->MaybeResolve(aCx, aData);
+  } else {
+    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+  }
+}
+
+// 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)
+    : mActor(aActor),
+      mMessageName(aMetadata.messageName()),
+      mQueryId(aMetadata.queryId()) {}
+
+void JSWindowActor::QueryHandler::RejectedCallback(
+    JSContext* aCx, JS::Handle<JS::Value> aValue) {
+  if (!mActor) {
+    return;
+  }
+
+  // Make sure that this rejection is reported, despite being "handled". This
+  // is done by creating a new promise in the rejected state, and throwing it
+  // away. This will be reported as an unhandled rejected promise.
+  Unused << JS::CallOriginalPromiseReject(aCx, aValue);
+
+  // The exception probably isn't cloneable, so just send down undefined.
+  SendReply(aCx, JSWindowActorMessageKind::QueryReject,
+            ipc::StructuredCloneData());
+}
+
+void JSWindowActor::QueryHandler::ResolvedCallback(
+    JSContext* aCx, JS::Handle<JS::Value> aValue) {
+  if (!mActor) {
+    return;
+  }
+
+  ipc::StructuredCloneData data;
+  data.InitScope(JS::StructuredCloneScope::DifferentProcess);
+
+  IgnoredErrorResult error;
+  data.Write(aCx, aValue, error);
+  if (NS_WARN_IF(error.Failed())) {
+    // We failed to serialize the message over IPC. Report this error to the
+    // console, and send a reject reply.
+    nsAutoString msg;
+    msg.Append(mActor->Name());
+    msg.Append(':');
+    msg.Append(mMessageName);
+    msg.Append(NS_LITERAL_STRING(": message reply cannot be cloned."));
+    nsContentUtils::LogSimpleConsoleError(msg, "chrome", false, true);
+
+    JS_ClearPendingException(aCx);
+
+    SendReply(aCx, JSWindowActorMessageKind::QueryReject,
+              ipc::StructuredCloneData());
+    return;
+  }
+
+  SendReply(aCx, JSWindowActorMessageKind::QueryResolve, std::move(data));
+}
+
+void JSWindowActor::QueryHandler::SendReply(JSContext* aCx,
+                                            JSWindowActorMessageKind aKind,
+                                            ipc::StructuredCloneData&& aData) {
+  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());
+  mActor = 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)
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/JSWindowActor.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_JSWindowActor_h
+#define mozilla_dom_JSWindowActor_h
+
+#include "js/TypeDecls.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace dom {
+
+enum class JSWindowActorMessageKind {
+  Message,
+  Query,
+  QueryResolve,
+  QueryReject,
+  EndGuard_,
+};
+
+class JSWindowActorMessageMeta;
+class QueryPromiseHandler;
+
+// Common base class for JSWindowActor{Parent,Child}.
+class JSWindowActor : public nsISupports, public nsWrapperCache {
+ public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(JSWindowActor)
+
+  JSWindowActor();
+
+  const nsString& Name() const { return mName; }
+
+  void SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
+                        JS::Handle<JS::Value> aObj,
+                        JS::Handle<JS::Value> aTransfers, ErrorResult& aRv);
+
+  already_AddRefed<Promise> SendQuery(JSContext* aCx,
+                                      const nsAString& aMessageName,
+                                      JS::Handle<JS::Value> aObj,
+                                      JS::Handle<JS::Value> aTransfers,
+                                      ErrorResult& aRv);
+
+  void ReceiveRawMessage(const JSWindowActorMessageMeta& aMetadata,
+                         ipc::StructuredCloneData&& aData);
+
+  nsIGlobalObject* GetParentObject() const;
+
+  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,
+                              ErrorResult& aRv) = 0;
+
+  virtual ~JSWindowActor() = default;
+
+  void SetName(const nsAString& aName);
+
+ private:
+  void ReceiveMessageOrQuery(JSContext* aCx,
+                             const JSWindowActorMessageMeta& aMetadata,
+                             JS::Handle<JS::Value> aData, ErrorResult& aRv);
+
+  void ReceiveQueryReply(JSContext* aCx,
+                         const JSWindowActorMessageMeta& aMetadata,
+                         JS::Handle<JS::Value> aData, ErrorResult& aRv);
+
+  // 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);
+
+    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;
+    nsString mMessageName;
+    uint64_t mQueryId;
+  };
+
+  nsString mName;
+  nsRefPtrHashtable<nsUint64HashKey, Promise> mPendingQueries;
+  uint64_t mNextQueryId;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::JSWindowActorMessageKind>
+    : public ContiguousEnumSerializer<
+          mozilla::dom::JSWindowActorMessageKind,
+          mozilla::dom::JSWindowActorMessageKind::Message,
+          mozilla::dom::JSWindowActorMessageKind::EndGuard_> {};
+
+}  // namespace IPC
+
+#endif  // !defined(mozilla_dom_JSWindowActor_h)
--- a/dom/ipc/JSWindowActorChild.cpp
+++ b/dom/ipc/JSWindowActorChild.cpp
@@ -20,96 +20,71 @@ JSObject* JSWindowActorChild::WrapObject
   return JSWindowActorChild_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 WindowGlobalChild* JSWindowActorChild::Manager() const { return mManager; }
 
 void JSWindowActorChild::Init(const nsAString& aName,
                               WindowGlobalChild* aManager) {
   MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorChild twice!");
-  mName = aName;
+  SetName(aName);
   mManager = aManager;
 }
 
-nsISupports* JSWindowActorChild::GetParentObject() const {
-  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
-}
-
 namespace {
 
 class AsyncMessageToParent : public Runnable {
  public:
-  AsyncMessageToParent(const nsAString& aActorName,
-                       const nsAString& aMessageName,
+  AsyncMessageToParent(const JSWindowActorMessageMeta& aMetadata,
                        ipc::StructuredCloneData&& aData,
                        WindowGlobalParent* aParent)
       : mozilla::Runnable("WindowGlobalParent::HandleAsyncMessage"),
-        mActorName(aActorName),
-        mMessageName(aMessageName),
+        mMetadata(aMetadata),
         mData(std::move(aData)),
         mParent(aParent) {}
 
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread.");
-    mParent->HandleAsyncMessage(mActorName, mMessageName, mData);
+    mParent->ReceiveRawMessage(mMetadata, std::move(mData));
     return NS_OK;
   }
 
  private:
-  nsString mActorName;
-  nsString mMessageName;
+  JSWindowActorMessageMeta mMetadata;
   ipc::StructuredCloneData mData;
   RefPtr<WindowGlobalParent> mParent;
 };
 
 }  // anonymous namespace
 
-void JSWindowActorChild::SendAsyncMessage(JSContext* aCx,
-                                          const nsAString& aMessageName,
-                                          JS::Handle<JS::Value> aObj,
-                                          JS::Handle<JS::Value> aTransfers,
-                                          ErrorResult& aRv) {
-  // If we've closed our channel already, just raise an exception.
+void JSWindowActorChild::SendRawMessage(const JSWindowActorMessageMeta& aMeta,
+                                        ipc::StructuredCloneData&& aData,
+                                        ErrorResult& aRv) {
   if (NS_WARN_IF(!mManager || mManager->IsClosed())) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  // Serialize our object out to a StructuredCloneData.
-  ipc::StructuredCloneData data;
-  if (!aObj.isUndefined() && !nsFrameMessageManager::GetParamsForMessage(
-                                 aCx, aObj, aTransfers, data)) {
+  if (mManager->IsInProcess()) {
+    RefPtr<WindowGlobalParent> wgp = mManager->GetParentActor();
+    nsCOMPtr<nsIRunnable> runnable =
+        new AsyncMessageToParent(aMeta, std::move(aData), wgp);
+    NS_DispatchToMainThread(runnable.forget());
+    return;
+  }
+
+  // Cross-process case - send data over WindowGlobalChild to other side.
+  ClonedMessageData msgData;
+  ContentChild* cc = ContentChild::GetSingleton();
+  if (NS_WARN_IF(!aData.BuildClonedMessageDataForChild(cc, msgData))) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  // If we're an in-process, asynchronously fire off the callback for in-process
-  // loads.
-  if (mManager->IsInProcess()) {
-    RefPtr<WindowGlobalParent> parent = mManager->GetParentActor();
-    RefPtr<AsyncMessageToParent> ev =
-        new AsyncMessageToParent(mName, aMessageName, std::move(data), parent);
-    DebugOnly<nsresult> rv = NS_DispatchToMainThread(ev);
-    NS_WARNING_ASSERTION(
-        NS_SUCCEEDED(rv),
-        "JS Window Actor AsyncMessageToParent dispatch failed");
-    return;
-  }
-
-  // If we're a cross-process, send the async message over the corresponding
-  // actor.
-  ClonedMessageData msgData;
-  ContentChild* cc = ContentChild::GetSingleton();
-  if (!data.BuildClonedMessageDataForChild(cc, msgData)) {
-    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
-    return;
-  }
-
-  if (!mManager->SendAsyncMessage(mName, PromiseFlatString(aMessageName),
-                                  msgData)) {
+  if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData))) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 }
 
 Document* JSWindowActorChild::GetDocument(ErrorResult& aRv) {
   if (!mManager) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@@ -132,20 +107,22 @@ BrowsingContext* JSWindowActorChild::Get
 Nullable<WindowProxyHolder> JSWindowActorChild::GetContentWindow(
     ErrorResult& aRv) {
   if (BrowsingContext* bc = GetBrowsingContext(aRv)) {
     return WindowProxyHolder(bc);
   }
   return nullptr;
 }
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorChild)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_INHERITED(JSWindowActorChild, JSWindowActor, mManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(JSWindowActorChild,
+                                               JSWindowActor)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActorChild)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActorChild)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorChild)
+NS_INTERFACE_MAP_END_INHERITING(JSWindowActor)
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(JSWindowActorChild, mManager)
+NS_IMPL_ADDREF_INHERITED(JSWindowActorChild, JSWindowActor)
+NS_IMPL_RELEASE_INHERITED(JSWindowActorChild, JSWindowActor)
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/JSWindowActorChild.h
+++ b/dom/ipc/JSWindowActorChild.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_JSWindowActorChild_h
 #define mozilla_dom_JSWindowActorChild_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/JSWindowActor.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
 template <typename>
 struct Nullable;
@@ -25,46 +26,44 @@ class WindowGlobalChild;
 class WindowProxyHolder;
 
 }  // namespace dom
 }  // namespace mozilla
 
 namespace mozilla {
 namespace dom {
 
-class JSWindowActorChild final : public nsISupports, public nsWrapperCache {
+class JSWindowActorChild final : public JSWindowActor {
  public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(JSWindowActorChild)
-
- protected:
-  ~JSWindowActorChild() = default;
-
- public:
-  nsISupports* GetParentObject() const;
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(JSWindowActorChild,
+                                                         JSWindowActor)
 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<JSWindowActorChild> Constructor(GlobalObject& aGlobal,
                                                           ErrorResult& aRv) {
     return MakeAndAddRef<JSWindowActorChild>();
   }
 
   WindowGlobalChild* Manager() const;
   void Init(const nsAString& aName, WindowGlobalChild* aManager);
-  void SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
-                        JS::Handle<JS::Value> aObj,
-                        JS::Handle<JS::Value> aTransfers, ErrorResult& aRv);
 
   Document* GetDocument(ErrorResult& aRv);
   BrowsingContext* GetBrowsingContext(ErrorResult& aRv);
   Nullable<WindowProxyHolder> GetContentWindow(ErrorResult& aRv);
 
+ protected:
+  void SendRawMessage(const JSWindowActorMessageMeta& aMeta,
+                      ipc::StructuredCloneData&& aData,
+                      ErrorResult& aRv) override;
+
  private:
-  nsString mName;
+  ~JSWindowActorChild() = default;
+
   RefPtr<WindowGlobalChild> mManager;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_JSWindowActorChild_h
--- a/dom/ipc/JSWindowActorParent.cpp
+++ b/dom/ipc/JSWindowActorParent.cpp
@@ -17,105 +17,83 @@ JSObject* JSWindowActorParent::WrapObjec
   return JSWindowActorParent_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 WindowGlobalParent* JSWindowActorParent::Manager() const { return mManager; }
 
 void JSWindowActorParent::Init(const nsAString& aName,
                                WindowGlobalParent* aManager) {
   MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorParent twice!");
-  mName = aName;
+  SetName(aName);
   mManager = aManager;
 }
 
-nsISupports* JSWindowActorParent::GetParentObject() const {
-  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
-}
-
 namespace {
 
 class AsyncMessageToChild : public Runnable {
  public:
-  AsyncMessageToChild(const nsAString& aActorName,
-                      const nsAString& aMessageName,
+  AsyncMessageToChild(const JSWindowActorMessageMeta& aMetadata,
                       ipc::StructuredCloneData&& aData,
                       WindowGlobalChild* aChild)
       : mozilla::Runnable("WindowGlobalChild::HandleAsyncMessage"),
-        mActorName(aActorName),
-        mMessageName(aMessageName),
+        mMetadata(aMetadata),
         mData(std::move(aData)),
         mChild(aChild) {}
 
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread.");
-    mChild->HandleAsyncMessage(mActorName, mMessageName, mData);
+    mChild->ReceiveRawMessage(mMetadata, std::move(mData));
     return NS_OK;
   }
 
  private:
-  nsString mActorName;
-  nsString mMessageName;
+  JSWindowActorMessageMeta mMetadata;
   ipc::StructuredCloneData mData;
   RefPtr<WindowGlobalChild> mChild;
 };
 
 }  // anonymous namespace
 
-void JSWindowActorParent::SendAsyncMessage(JSContext* aCx,
-                                           const nsAString& aMessageName,
-                                           JS::Handle<JS::Value> aObj,
-                                           JS::Handle<JS::Value> aTransfers,
-                                           ErrorResult& aRv) {
-  // If we've closed our channel already, just raise a warning.
+void JSWindowActorParent::SendRawMessage(const JSWindowActorMessageMeta& aMeta,
+                                         ipc::StructuredCloneData&& aData,
+                                         ErrorResult& aRv) {
   if (NS_WARN_IF(!mManager || mManager->IsClosed())) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  // Serialize our object out to a StructuredCloneData.
-  ipc::StructuredCloneData data;
-  if (!aObj.isUndefined() && !nsFrameMessageManager::GetParamsForMessage(
-                                 aCx, aObj, aTransfers, data)) {
+  if (mManager->IsInProcess()) {
+    RefPtr<WindowGlobalChild> wgc = mManager->GetChildActor();
+    nsCOMPtr<nsIRunnable> runnable =
+        new AsyncMessageToChild(aMeta, std::move(aData), wgc);
+    NS_DispatchToMainThread(runnable.forget());
+    return;
+  }
+
+  // Cross-process case - send data over WindowGlobalParent to other side.
+  ClonedMessageData msgData;
+  RefPtr<TabParent> tabParent = mManager->GetTabParent();
+  ContentParent* cp = tabParent->Manager();
+  if (NS_WARN_IF(!aData.BuildClonedMessageDataForParent(cp, msgData))) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  // If we're an in-process, asynchronously fire off the callback for in-process
-  // loads.
-  if (mManager->IsInProcess()) {
-    RefPtr<WindowGlobalChild> child = mManager->GetChildActor();
-    RefPtr<AsyncMessageToChild> ev =
-        new AsyncMessageToChild(mName, aMessageName, std::move(data), child);
-    DebugOnly<nsresult> rv = NS_DispatchToMainThread(ev);
-    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
-                         "JS Window Actor AsyncMessageToChild dispatch failed");
-    return;
-  }
-
-  // If we're a cross-process, send the async message over the corresponding
-  // actor.
-  ClonedMessageData msgData;
-  RefPtr<TabParent> tabParent = mManager->GetTabParent();
-  ContentParent* cp = tabParent->Manager();
-  if (!data.BuildClonedMessageDataForParent(cp, msgData)) {
-    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
-    return;
-  }
-
-  if (!mManager->SendAsyncMessage(mName, PromiseFlatString(aMessageName),
-                                  msgData)) {
+  if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData))) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 }
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorParent)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_INHERITED(JSWindowActorParent, JSWindowActor, mManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(JSWindowActorParent,
+                                               JSWindowActor)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActorParent)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActorParent)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorParent)
+NS_INTERFACE_MAP_END_INHERITING(JSWindowActor)
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(JSWindowActorParent, mManager)
+NS_IMPL_ADDREF_INHERITED(JSWindowActorParent, JSWindowActor)
+NS_IMPL_RELEASE_INHERITED(JSWindowActorParent, JSWindowActor)
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/JSWindowActorParent.h
+++ b/dom/ipc/JSWindowActorParent.h
@@ -6,56 +6,55 @@
 
 #ifndef mozilla_dom_JSWindowActorParent_h
 #define mozilla_dom_JSWindowActorParent_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/JSWindowActor.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
 class WindowGlobalParent;
 
 }  // namespace dom
 }  // namespace mozilla
 
 namespace mozilla {
 namespace dom {
 
-class JSWindowActorParent final : public nsISupports, public nsWrapperCache {
+class JSWindowActorParent final : public JSWindowActor {
  public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(JSWindowActorParent)
-
- protected:
-  ~JSWindowActorParent() = default;
-
- public:
-  nsISupports* GetParentObject() const;
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(JSWindowActorParent,
+                                                         JSWindowActor)
 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<JSWindowActorParent> Constructor(
       GlobalObject& aGlobal, ErrorResult& aRv) {
     return MakeAndAddRef<JSWindowActorParent>();
   }
 
   WindowGlobalParent* Manager() const;
   void Init(const nsAString& aName, WindowGlobalParent* aManager);
-  void SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
-                        JS::Handle<JS::Value> aObj,
-                        JS::Handle<JS::Value> aTransfers, ErrorResult& aRv);
+
+ protected:
+  void SendRawMessage(const JSWindowActorMessageMeta& aMeta,
+                      ipc::StructuredCloneData&& aData,
+                      ErrorResult& aRv) override;
 
  private:
-  nsString mName;
+  ~JSWindowActorParent() = default;
+
   RefPtr<WindowGlobalParent> mManager;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_JSWindowActorParent_h
--- a/dom/ipc/PWindowGlobal.ipdl
+++ b/dom/ipc/PWindowGlobal.ipdl
@@ -5,20 +5,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBrowser;
 include protocol PInProcess;
 include protocol PBrowserBridge;
 
 include DOMTypes;
 
+using JSWindowActorMessageKind from "mozilla/dom/JSWindowActor.h";
 
 namespace mozilla {
 namespace dom {
 
+struct JSWindowActorMessageMeta {
+  nsString actorName;
+  nsString messageName;
+  uint64_t queryId;
+  JSWindowActorMessageKind kind;
+};
+
 /**
  * A PWindowGlobal actor has a lifetime matching that of a single Window Global,
  * specifically a |nsGlobalWindowInner|. These actors will form a parent/child
  * link either between the chrome/content process, or will be in-process, for
  * documents which are loaded in the chrome process.
  */
 async protocol PWindowGlobal
 {
@@ -28,17 +36,17 @@ child:
   async __delete__();
 
   async ChangeFrameRemoteness(BrowsingContext aFrameContext,
                               nsString aRemoteType,
                               uint64_t aSwitchId)
       returns (nsresult rv, nullable PBrowserBridge bridge);
 
 both:
-  async AsyncMessage(nsString aActorName, nsString aMessage, ClonedMessageData aData);
+  async RawMessage(JSWindowActorMessageMeta aMetadata, ClonedMessageData aData);
 
 parent:
   /// Update the URI of the document in this WindowGlobal.
   async UpdateDocumentURI(nsIURI aUri);
 
   /// Notify the parent that this PWindowGlobal is now the current global.
   async BecomeCurrentWindowGlobal();
 
--- a/dom/ipc/WindowGlobalChild.cpp
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -2,24 +2,28 @@
 /* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/WindowGlobalChild.h"
 
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/BrowserBridgeChild.h"
 #include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/WindowGlobalActorsBinding.h"
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozilla/ipc/InProcessChild.h"
 #include "nsDocShell.h"
 #include "nsGlobalWindowInner.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsQueryObject.h"
 
 #include "mozilla/dom/JSWindowActorBinding.h"
 #include "mozilla/dom/JSWindowActorChild.h"
 #include "mozilla/dom/JSWindowActorService.h"
 #include "nsIHttpChannelInternal.h"
 
 using namespace mozilla::ipc;
 using namespace mozilla::dom::ipc;
@@ -212,57 +216,40 @@ IPCResult WindowGlobalChild::RecvChangeF
   nsresult rv = ChangeFrameRemoteness(this, aBc, aRemoteType, aPendingSwitchId,
                                       getter_AddRefs(bbc));
 
   // To make the type system happy, we've gotta do some gymnastics.
   aResolver(Tuple<const nsresult&, PBrowserBridgeChild*>(rv, bbc));
   return IPC_OK();
 }
 
-IPCResult WindowGlobalChild::RecvAsyncMessage(const nsString& aActorName,
-                                              const nsString& aMessageName,
-                                              const ClonedMessageData& aData) {
+IPCResult WindowGlobalChild::RecvRawMessage(
+    const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData) {
   StructuredCloneData data;
   data.BorrowFromClonedMessageDataForChild(aData);
-  HandleAsyncMessage(aActorName, aMessageName, data);
+  ReceiveRawMessage(aMeta, std::move(data));
   return IPC_OK();
 }
 
-void WindowGlobalChild::HandleAsyncMessage(const nsString& aActorName,
-                                           const nsString& aMessageName,
-                                           StructuredCloneData& aData) {
-  if (NS_WARN_IF(mIPCClosed)) {
-    return;
-  }
-
-  // Force creation of the actor if it hasn't been created yet.
-  IgnoredErrorResult rv;
-  RefPtr<JSWindowActorChild> actor = GetActor(aActorName, rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    return;
+void WindowGlobalChild::ReceiveRawMessage(const JSWindowActorMessageMeta& aMeta,
+                                          StructuredCloneData&& aData) {
+  RefPtr<JSWindowActorChild> actor =
+      GetActor(aMeta.actorName(), IgnoreErrors());
+  if (actor) {
+    actor->ReceiveRawMessage(aMeta, std::move(aData));
   }
-
-  // Get the JSObject for the named actor.
-  JS::RootedObject obj(RootingCx(), actor->GetWrapper());
-  if (NS_WARN_IF(!obj)) {
-    // If we don't have a preserved wrapper, there won't be any receiver
-    // method to call.
-    return;
-  }
-
-  RefPtr<JSWindowActorService> actorSvc = JSWindowActorService::GetSingleton();
-  if (NS_WARN_IF(!actorSvc)) {
-    return;
-  }
-
-  actorSvc->ReceiveMessage(actor, obj, aMessageName, aData);
 }
 
 already_AddRefed<JSWindowActorChild> WindowGlobalChild::GetActor(
     const nsAString& aName, ErrorResult& aRv) {
+  if (mIPCClosed) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
   // Check if this actor has already been created, and return it if it has.
   if (mWindowActors.Contains(aName)) {
     return do_AddRef(mWindowActors.GetWeak(aName));
   }
 
   // Otherwise, we want to create a new instance of this actor. Call into the
   // JSWindowActorService to trigger construction.
   RefPtr<JSWindowActorService> actorSvc = JSWindowActorService::GetSingleton();
@@ -296,16 +283,23 @@ already_AddRefed<JSWindowActorChild> Win
   actor->Init(aName, this);
   mWindowActors.Put(aName, actor);
   return actor.forget();
 }
 
 void WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy) {
   mIPCClosed = true;
   gWindowGlobalChildById->Remove(mInnerWindowId);
+
+  // Destroy our JSWindowActors, and reject any pending queries.
+  nsRefPtrHashtable<nsStringHashKey, JSWindowActorChild> windowActors;
+  mWindowActors.SwapElements(windowActors);
+  for (auto iter = windowActors.Iter(); !iter.Done(); iter.Next()) {
+    iter.Data()->RejectPendingQueries();
+  }
 }
 
 WindowGlobalChild::~WindowGlobalChild() {
   MOZ_ASSERT(!gWindowGlobalChildById ||
              !gWindowGlobalChildById->Contains(mInnerWindowId));
 }
 
 JSObject* WindowGlobalChild::WrapObject(JSContext* aCx,
--- a/dom/ipc/WindowGlobalChild.h
+++ b/dom/ipc/WindowGlobalChild.h
@@ -16,16 +16,17 @@ class nsGlobalWindowInner;
 class nsDocShell;
 
 namespace mozilla {
 namespace dom {
 
 class BrowsingContext;
 class WindowGlobalParent;
 class JSWindowActorChild;
+class JSWindowActorMessageMeta;
 class TabChild;
 
 /**
  * Actor for a single nsGlobalWindowInner. This actor is used to communicate
  * information to the parent process asynchronously.
  */
 class WindowGlobalChild : public nsWrapperCache, public PWindowGlobalChild {
   friend class PWindowGlobalChild;
@@ -62,37 +63,35 @@ class WindowGlobalChild : public nsWrapp
   // Get the other side of this actor if it is an in-process actor. Returns
   // |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<TabChild> GetTabChild();
 
-  void HandleAsyncMessage(const nsString& aActorName,
-                          const nsString& aMessageName,
-                          ipc::StructuredCloneData& aData);
+  void ReceiveRawMessage(const JSWindowActorMessageMeta& aMeta,
+                         ipc::StructuredCloneData&& aData);
 
   // 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);
 
   nsISupports* GetParentObject();
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
  protected:
   // IPC messages
-  mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aActorName,
-                                           const nsString& aMessage,
-                                           const ClonedMessageData& aData);
+  mozilla::ipc::IPCResult RecvRawMessage(const JSWindowActorMessageMeta& aMeta,
+                                         const ClonedMessageData& aData);
 
   mozilla::ipc::IPCResult RecvChangeFrameRemoteness(
       dom::BrowsingContext* aBc, const nsString& aRemoteType,
       uint64_t aPendingSwitchId, ChangeFrameRemotenessResolver&& aResolver);
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
--- a/dom/ipc/WindowGlobalParent.cpp
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -170,57 +170,40 @@ IPCResult WindowGlobalParent::RecvDestro
     RefPtr<TabParent> tabParent = GetTabParent();
     if (!tabParent || !tabParent->IsDestroyed()) {
       Unused << Send__delete__(this);
     }
   }
   return IPC_OK();
 }
 
-IPCResult WindowGlobalParent::RecvAsyncMessage(const nsString& aActorName,
-                                               const nsString& aMessageName,
-                                               const ClonedMessageData& aData) {
+IPCResult WindowGlobalParent::RecvRawMessage(
+    const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData) {
   StructuredCloneData data;
   data.BorrowFromClonedMessageDataForParent(aData);
-  HandleAsyncMessage(aActorName, aMessageName, data);
+  ReceiveRawMessage(aMeta, std::move(data));
   return IPC_OK();
 }
 
-void WindowGlobalParent::HandleAsyncMessage(const nsString& aActorName,
-                                            const nsString& aMessageName,
-                                            StructuredCloneData& aData) {
-  if (NS_WARN_IF(mIPCClosed)) {
-    return;
-  }
-
-  // Force creation of the actor if it hasn't been created yet.
-  IgnoredErrorResult rv;
-  RefPtr<JSWindowActorParent> actor = GetActor(aActorName, rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    return;
+void WindowGlobalParent::ReceiveRawMessage(
+    const JSWindowActorMessageMeta& aMeta, StructuredCloneData&& aData) {
+  RefPtr<JSWindowActorParent> actor =
+      GetActor(aMeta.actorName(), IgnoreErrors());
+  if (actor) {
+    actor->ReceiveRawMessage(aMeta, std::move(aData));
   }
-
-  // Get the JSObject for the named actor.
-  JS::RootedObject obj(RootingCx(), actor->GetWrapper());
-  if (NS_WARN_IF(!obj)) {
-    // If we don't have a preserved wrapper, there won't be any receiver
-    // method to call.
-    return;
-  }
-
-  RefPtr<JSWindowActorService> actorSvc = JSWindowActorService::GetSingleton();
-  if (NS_WARN_IF(!actorSvc)) {
-    return;
-  }
-
-  actorSvc->ReceiveMessage(actor, obj, aMessageName, aData);
 }
 
 already_AddRefed<JSWindowActorParent> WindowGlobalParent::GetActor(
     const nsAString& aName, ErrorResult& aRv) {
+  if (mIPCClosed) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
   // Check if this actor has already been created, and return it if it has.
   if (mWindowActors.Contains(aName)) {
     return do_AddRef(mWindowActors.GetWeak(aName));
   }
 
   // Otherwise, we want to create a new instance of this actor. Call into the
   // JSWindowActorService to trigger construction
   RefPtr<JSWindowActorService> actorSvc = JSWindowActorService::GetSingleton();
@@ -311,16 +294,23 @@ already_AddRefed<Promise> WindowGlobalPa
   return promise.forget();
 }
 
 void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) {
   mIPCClosed = true;
   gWindowGlobalParentsById->Remove(mInnerWindowId);
   mBrowsingContext->UnregisterWindowGlobal(this);
 
+  // Destroy our JSWindowActors, and reject any pending queries.
+  nsRefPtrHashtable<nsStringHashKey, JSWindowActorParent> windowActors;
+  mWindowActors.SwapElements(windowActors);
+  for (auto iter = windowActors.Iter(); !iter.Done(); iter.Next()) {
+    iter.Data()->RejectPendingQueries();
+  }
+
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (obs) {
     obs->NotifyObservers(this, "window-global-destroyed", nullptr);
   }
 }
 
 WindowGlobalParent::~WindowGlobalParent() {
   MOZ_ASSERT(!gWindowGlobalParentsById ||
--- a/dom/ipc/WindowGlobalParent.h
+++ b/dom/ipc/WindowGlobalParent.h
@@ -19,16 +19,17 @@ class nsIURI;
 class nsFrameLoader;
 
 namespace mozilla {
 namespace dom {
 
 class CanonicalBrowsingContext;
 class WindowGlobalChild;
 class JSWindowActorParent;
+class JSWindowActorMessageMeta;
 
 /**
  * A handle in the parent process to a specific nsGlobalWindowInner object.
  */
 class WindowGlobalParent final : public nsISupports,
                                  public nsWrapperCache,
                                  public PWindowGlobalParent {
   friend class PWindowGlobalParent;
@@ -59,19 +60,18 @@ class WindowGlobalParent final : public 
   // Get a JS actor object by name.
   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<TabParent> GetTabParent();
 
-  void HandleAsyncMessage(const nsString& aActorName,
-                          const nsString& aMessageName,
-                          ipc::StructuredCloneData& aData);
+  void ReceiveRawMessage(const JSWindowActorMessageMeta& aMeta,
+                         ipc::StructuredCloneData&& aData);
 
   // 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() { return mBrowsingContext; }
@@ -108,19 +108,18 @@ class WindowGlobalParent final : public 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
  protected:
   // IPC messages
   mozilla::ipc::IPCResult RecvUpdateDocumentURI(nsIURI* aURI);
   mozilla::ipc::IPCResult RecvBecomeCurrentWindowGlobal();
   mozilla::ipc::IPCResult RecvDestroy();
-  mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aActorName,
-                                           const nsString& aMessageName,
-                                           const ClonedMessageData& aData);
+  mozilla::ipc::IPCResult RecvRawMessage(const JSWindowActorMessageMeta& aMeta,
+                                         const ClonedMessageData& aData);
   mozilla::ipc::IPCResult RecvDidEmbedBrowsingContext(
       dom::BrowsingContext* aContext);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   ~WindowGlobalParent();
 
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -42,16 +42,17 @@ EXPORTS.mozilla.dom += [
     'ContentChild.h',
     'ContentParent.h',
     'ContentProcess.h',
     'ContentProcessManager.h',
     'CPOWManagerGetter.h',
     'CSPMessageUtils.h',
     'DocShellMessageUtils.h',
     'FilePickerParent.h',
+    'JSWindowActor.h',
     'JSWindowActorChild.h',
     'JSWindowActorParent.h',
     'JSWindowActorService.h',
     'MemoryReportRequest.h',
     'PermissionMessageUtils.h',
     'ReferrerInfoUtils.h',
     'RemoteWebProgress.h',
     'RemoteWebProgressRequest.h',
@@ -79,16 +80,17 @@ UNIFIED_SOURCES += [
     'CoalescedWheelData.cpp',
     'ColorPickerParent.cpp',
     'ContentParent.cpp',
     'ContentProcess.cpp',
     'ContentProcessManager.cpp',
     'CSPMessageUtils.cpp',
     'DocShellMessageUtils.cpp',
     'FilePickerParent.cpp',
+    'JSWindowActor.cpp',
     'JSWindowActorChild.cpp',
     'JSWindowActorParent.cpp',
     'JSWindowActorService.cpp',
     'MemMapSnapshot.cpp',
     'MemoryReportRequest.cpp',
     'MMPrinter.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',