Bug 1660539 - Make StructuredCloneData args optional in JSActor RawMessage. r=kmag, a=jcristau FIREFOX_82_0_1_BUILD1 FIREFOX_82_0_1_RELEASE
authorNika Layzell <nika@thelayzells.com>
Tue, 13 Oct 2020 17:43:56 +0000
changeset 618983 86c6ac60afca3f420110dcfb855d0dbab801f88c
parent 618982 368d17ec7a2df69a349a0ca1e6a48c1368c5555e
child 618984 6482b14a6cd804a526e62b62e61a9ec43b502b87
push id2440
push userjcristau@mozilla.com
push dateMon, 26 Oct 2020 15:37:33 +0000
treeherdermozilla-release@86c6ac60afca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag, jcristau
bugs1660539
milestone82.0.1
Bug 1660539 - Make StructuredCloneData args optional in JSActor RawMessage. r=kmag, a=jcristau Differential Revision: https://phabricator.services.mozilla.com/D93122
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/PWindowGlobal.ipdl
dom/ipc/WindowGlobalChild.cpp
dom/ipc/WindowGlobalChild.h
dom/ipc/WindowGlobalParent.cpp
dom/ipc/WindowGlobalParent.h
dom/ipc/jsactor/JSActor.cpp
dom/ipc/jsactor/JSActor.h
dom/ipc/jsactor/JSActorManager.cpp
dom/ipc/jsactor/JSActorManager.h
dom/ipc/jsactor/JSProcessActorChild.cpp
dom/ipc/jsactor/JSProcessActorChild.h
dom/ipc/jsactor/JSProcessActorParent.cpp
dom/ipc/jsactor/JSProcessActorParent.h
dom/ipc/jsactor/JSWindowActorChild.cpp
dom/ipc/jsactor/JSWindowActorChild.h
dom/ipc/jsactor/JSWindowActorParent.cpp
dom/ipc/jsactor/JSWindowActorParent.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -4356,22 +4356,28 @@ already_AddRefed<JSActor> ContentChild::
 
   MOZ_RELEASE_ASSERT(!actor->Manager(),
                      "mManager was already initialized once!");
   actor->Init(aName, this);
   return actor.forget();
 }
 
 IPCResult ContentChild::RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                       const ClonedMessageData& aData,
-                                       const ClonedMessageData& aStack) {
-  StructuredCloneData data;
-  data.BorrowFromClonedMessageDataForChild(aData);
-  StructuredCloneData stack;
-  stack.BorrowFromClonedMessageDataForChild(aStack);
+                                       const Maybe<ClonedMessageData>& aData,
+                                       const Maybe<ClonedMessageData>& aStack) {
+  Maybe<StructuredCloneData> data;
+  if (aData) {
+    data.emplace();
+    data->BorrowFromClonedMessageDataForChild(*aData);
+  }
+  Maybe<StructuredCloneData> stack;
+  if (aStack) {
+    stack.emplace();
+    stack->BorrowFromClonedMessageDataForChild(*aStack);
+  }
   ReceiveRawMessage(aMeta, std::move(data), std::move(stack));
   return IPC_OK();
 }
 
 NS_IMETHODIMP ContentChild::GetCanSend(bool* aCanSend) {
   *aCanSend = CanSend();
   return NS_OK;
 }
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -793,19 +793,19 @@ class ContentChild final : public PConte
       const Maybe<int32_t>& aCancelContentJSEpoch);
   mozilla::ipc::IPCResult RecvReload(
       const MaybeDiscarded<BrowsingContext>& aContext,
       const uint32_t aReloadFlags);
   mozilla::ipc::IPCResult RecvStopLoad(
       const MaybeDiscarded<BrowsingContext>& aContext,
       const uint32_t aStopFlags);
 
-  mozilla::ipc::IPCResult RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                         const ClonedMessageData& aData,
-                                         const ClonedMessageData& aStack);
+  mozilla::ipc::IPCResult RecvRawMessage(
+      const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
+      const Maybe<ClonedMessageData>& aStack);
 
   already_AddRefed<JSActor> InitJSActor(JS::HandleObject aMaybeActor,
                                         const nsACString& aName,
                                         ErrorResult& aRv) override;
   mozilla::ipc::IProtocol* AsNativeActor() override { return this; }
 
   mozilla::ipc::IPCResult RecvHistoryCommitIndexAndLength(
       const MaybeDiscarded<BrowsingContext>& aContext, const uint32_t& aIndex,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -7061,23 +7061,29 @@ NS_IMETHODIMP ContentParent::GetOsPid(in
   return NS_OK;
 }
 
 NS_IMETHODIMP ContentParent::GetRemoteType(nsACString& aRemoteType) {
   aRemoteType = GetRemoteType();
   return NS_OK;
 }
 
-IPCResult ContentParent::RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                        const ClonedMessageData& aData,
-                                        const ClonedMessageData& aStack) {
-  StructuredCloneData data;
-  data.BorrowFromClonedMessageDataForParent(aData);
-  StructuredCloneData stack;
-  stack.BorrowFromClonedMessageDataForParent(aStack);
+IPCResult ContentParent::RecvRawMessage(
+    const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
+    const Maybe<ClonedMessageData>& aStack) {
+  Maybe<StructuredCloneData> data;
+  if (aData) {
+    data.emplace();
+    data->BorrowFromClonedMessageDataForParent(*aData);
+  }
+  Maybe<StructuredCloneData> stack;
+  if (aStack) {
+    stack.emplace();
+    stack->BorrowFromClonedMessageDataForParent(*aStack);
+  }
   ReceiveRawMessage(aMeta, std::move(data), std::move(stack));
   return IPC_OK();
 }
 
 NS_IMETHODIMP ContentParent::GetActor(const nsACString& aName, JSContext* aCx,
                                       JSProcessActorParent** retval) {
   ErrorResult error;
   RefPtr<JSProcessActorParent> actor =
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1296,19 +1296,19 @@ class ContentParent final
       uint64_t aTopContextId, const nsACString& aOriginAttrs,
       const nsACString& aOriginKey, const nsTArray<KeyValuePair>& aDefaultData,
       const nsTArray<KeyValuePair>& aSessionData);
 
   mozilla::ipc::IPCResult RecvReportServiceWorkerShutdownProgress(
       uint32_t aShutdownStateId,
       ServiceWorkerShutdownState::Progress aProgress);
 
-  mozilla::ipc::IPCResult RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                         const ClonedMessageData& aData,
-                                         const ClonedMessageData& aStack);
+  mozilla::ipc::IPCResult RecvRawMessage(
+      const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
+      const Maybe<ClonedMessageData>& aStack);
 
   mozilla::ipc::IPCResult RecvAbortOtherOrientationPendingPromises(
       const MaybeDiscarded<BrowsingContext>& aContext);
 
   mozilla::ipc::IPCResult RecvNotifyOnHistoryReload(
       const MaybeDiscarded<BrowsingContext>& aContext,
       NotifyOnHistoryReloadResolver&& aResolver);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -466,18 +466,18 @@ both:
 
     // For parent->child, aBrowser must be non-null; aContext can
     // be null to indicate the browser's current root document, or non-null
     // to persist a subdocument.  For child->parent, arguments are
     // ignored and should be null.
     async PWebBrowserPersistDocument(nullable PBrowser aBrowser,
                                      MaybeDiscardedBrowsingContext aContext);
 
-    async RawMessage(JSActorMessageMeta aMetadata, ClonedMessageData aData,
-                     ClonedMessageData aStack);
+    async RawMessage(JSActorMessageMeta aMetadata, ClonedMessageData? aData,
+                     ClonedMessageData? aStack);
 
 child:
     async InitGMPService(Endpoint<PGMPServiceChild> service);
     async InitProcessHangMonitor(Endpoint<PProcessHangMonitorChild> hangMonitor);
     async InitProfiler(Endpoint<PProfilerChild> aEndpoint);
 
     // Give the content process its endpoints to the compositor.
     async InitRendering(
--- a/dom/ipc/PWindowGlobal.ipdl
+++ b/dom/ipc/PWindowGlobal.ipdl
@@ -70,18 +70,18 @@ child:
 
   async DispatchSecurityPolicyViolation(nsString aViolationEventJSON);
 
   async SaveStorageAccessPermissionGranted();
 
   async AddBlockedFrameNodeByClassifier(MaybeDiscardedBrowsingContext aNode);
 
 both:
-  async RawMessage(JSActorMessageMeta aMetadata, ClonedMessageData aData,
-                   ClonedMessageData aStack);
+  async RawMessage(JSActorMessageMeta 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(MaybeDiscardedBrowsingContext aTargetBC,
                 nsDocShellLoadState aLoadState, bool aSetNavigating);
 
--- a/dom/ipc/WindowGlobalChild.cpp
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -540,23 +540,29 @@ mozilla::ipc::IPCResult WindowGlobalChil
     return IPC_OK();
   }
 
   MOZ_ASSERT(aNode.get()->GetEmbedderElement()->OwnerDoc() == doc);
   doc->AddBlockedNodeByClassifier(aNode.get()->GetEmbedderElement());
   return IPC_OK();
 }
 
-IPCResult WindowGlobalChild::RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                            const ClonedMessageData& aData,
-                                            const ClonedMessageData& aStack) {
-  StructuredCloneData data;
-  data.BorrowFromClonedMessageDataForChild(aData);
-  StructuredCloneData stack;
-  stack.BorrowFromClonedMessageDataForChild(aStack);
+IPCResult WindowGlobalChild::RecvRawMessage(
+    const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
+    const Maybe<ClonedMessageData>& aStack) {
+  Maybe<StructuredCloneData> data;
+  if (aData) {
+    data.emplace();
+    data->BorrowFromClonedMessageDataForChild(*aData);
+  }
+  Maybe<StructuredCloneData> stack;
+  if (aStack) {
+    stack.emplace();
+    stack->BorrowFromClonedMessageDataForChild(*aStack);
+  }
   ReceiveRawMessage(aMeta, std::move(data), std::move(stack));
   return IPC_OK();
 }
 
 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
--- a/dom/ipc/WindowGlobalChild.h
+++ b/dom/ipc/WindowGlobalChild.h
@@ -119,19 +119,19 @@ class WindowGlobalChild final : public W
   const nsACString& GetRemoteType() override;
 
   already_AddRefed<JSActor> InitJSActor(JS::HandleObject aMaybeActor,
                                         const nsACString& aName,
                                         ErrorResult& aRv) override;
   mozilla::ipc::IProtocol* AsNativeActor() override { return this; }
 
   // IPC messages
-  mozilla::ipc::IPCResult RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                         const ClonedMessageData& aData,
-                                         const ClonedMessageData& aStack);
+  mozilla::ipc::IPCResult RecvRawMessage(
+      const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
+      const Maybe<ClonedMessageData>& aStack);
 
   mozilla::ipc::IPCResult RecvMakeFrameLocal(
       const MaybeDiscarded<dom::BrowsingContext>& aFrameContext,
       uint64_t aPendingSwitchId);
 
   mozilla::ipc::IPCResult RecvMakeFrameRemote(
       const MaybeDiscarded<dom::BrowsingContext>& aFrameContext,
       ManagedEndpoint<PBrowserBridgeChild>&& aEndpoint, const TabId& aTabId,
--- a/dom/ipc/WindowGlobalParent.cpp
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -412,23 +412,29 @@ IPCResult WindowGlobalParent::RecvDestro
     RefPtr<BrowserParent> browserParent = GetBrowserParent();
     if (!browserParent || !browserParent->IsDestroyed()) {
       Unused << Send__delete__(this);
     }
   }
   return IPC_OK();
 }
 
-IPCResult WindowGlobalParent::RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                             const ClonedMessageData& aData,
-                                             const ClonedMessageData& aStack) {
-  StructuredCloneData data;
-  data.BorrowFromClonedMessageDataForParent(aData);
-  StructuredCloneData stack;
-  stack.BorrowFromClonedMessageDataForParent(aStack);
+IPCResult WindowGlobalParent::RecvRawMessage(
+    const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
+    const Maybe<ClonedMessageData>& aStack) {
+  Maybe<StructuredCloneData> data;
+  if (aData) {
+    data.emplace();
+    data->BorrowFromClonedMessageDataForParent(*aData);
+  }
+  Maybe<StructuredCloneData> stack;
+  if (aStack) {
+    stack.emplace();
+    stack->BorrowFromClonedMessageDataForParent(*aStack);
+  }
   ReceiveRawMessage(aMeta, std::move(data), std::move(stack));
   return IPC_OK();
 }
 
 const nsACString& WindowGlobalParent::GetRemoteType() {
   if (RefPtr<BrowserParent> browserParent = GetBrowserParent()) {
     return browserParent->Manager()->GetRemoteType();
   }
--- a/dom/ipc/WindowGlobalParent.h
+++ b/dom/ipc/WindowGlobalParent.h
@@ -224,19 +224,19 @@ class WindowGlobalParent final : public 
     return IPC_OK();
   }
   mozilla::ipc::IPCResult RecvUpdateDocumentSecurityInfo(
       nsITransportSecurityInfo* aSecurityInfo);
   mozilla::ipc::IPCResult RecvSetHasBeforeUnload(bool aHasBeforeUnload);
   mozilla::ipc::IPCResult RecvSetClientInfo(
       const IPCClientInfo& aIPCClientInfo);
   mozilla::ipc::IPCResult RecvDestroy();
-  mozilla::ipc::IPCResult RecvRawMessage(const JSActorMessageMeta& aMeta,
-                                         const ClonedMessageData& aData,
-                                         const ClonedMessageData& aStack);
+  mozilla::ipc::IPCResult RecvRawMessage(
+      const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
+      const Maybe<ClonedMessageData>& aStack);
 
   mozilla::ipc::IPCResult RecvGetContentBlockingEvents(
       GetContentBlockingEventsResolver&& aResolver);
   mozilla::ipc::IPCResult RecvUpdateCookieJarSettings(
       const CookieJarSettingsArgs& aCookieJarSettingsArgs);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
--- a/dom/ipc/jsactor/JSActor.cpp
+++ b/dom/ipc/jsactor/JSActor.cpp
@@ -151,60 +151,52 @@ bool JSActor::AllowMessage(const JSActor
   return false;
 }
 
 void JSActor::SetName(const nsACString& aName) {
   MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!");
   mName = aName;
 }
 
-static ipc::StructuredCloneData CloneOrUndefined(
-    JSContext* aCx, JS::Handle<JS::Value> aValue,
-    FunctionRef<void()> aOnError = nullptr) {
-  ipc::StructuredCloneData data;
+static Maybe<ipc::StructuredCloneData> TryClone(JSContext* aCx,
+                                                JS::Handle<JS::Value> aValue) {
+  Maybe<ipc::StructuredCloneData> data{std::in_place};
 
   // Try to directly serialize the passed-in data, and return it to our caller.
   IgnoredErrorResult rv;
-  data.Write(aCx, aValue, rv);
+  data->Write(aCx, aValue, rv);
   if (rv.Failed()) {
-    rv.SuppressException();
+    // Serialization failed, return `Nothing()` instead.
     JS_ClearPendingException(aCx);
-    if (aOnError) {
-      aOnError();
-    }
-
-    // If the serialization failed, write out `undefined` instead.
-    data = ipc::StructuredCloneData();
-    data.Write(aCx, JS::UndefinedHandleValue, rv);
-    MOZ_RELEASE_ASSERT(!rv.Failed(), "OOM while serializing 'undefined'");
+    data.reset();
   }
   return data;
 }
 
-static ipc::StructuredCloneData CloneJSStack(JSContext* aCx,
-                                             JS::Handle<JSObject*> aStack) {
+static Maybe<ipc::StructuredCloneData> CloneJSStack(
+    JSContext* aCx, JS::Handle<JSObject*> aStack) {
   JS::Rooted<JS::Value> stackVal(aCx, JS::ObjectOrNullValue(aStack));
-  return CloneOrUndefined(aCx, stackVal);
+  return TryClone(aCx, stackVal);
 }
 
-static ipc::StructuredCloneData CaptureJSStack(JSContext* aCx) {
+static Maybe<ipc::StructuredCloneData> CaptureJSStack(JSContext* aCx) {
   JS::Rooted<JSObject*> stack(aCx, nullptr);
   if (JS::IsAsyncStackCaptureEnabledForRealm(aCx) &&
       !JS::CaptureCurrentStack(aCx, &stack)) {
     JS_ClearPendingException(aCx);
   }
 
   return CloneJSStack(aCx, stack);
 }
 
 void JSActor::SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
                                JS::Handle<JS::Value> aObj, ErrorResult& aRv) {
-  ipc::StructuredCloneData data;
+  Maybe<ipc::StructuredCloneData> data{std::in_place};
   if (!nsFrameMessageManager::GetParamsForMessage(
-          aCx, aObj, JS::UndefinedHandleValue, data)) {
+          aCx, aObj, JS::UndefinedHandleValue, *data)) {
     aRv.ThrowDataCloneError(nsPrintfCString(
         "Failed to serialize message '%s::%s'",
         NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
     return;
   }
 
   JSActorMessageMeta meta;
   meta.actorName() = mName;
@@ -213,19 +205,19 @@ void JSActor::SendAsyncMessage(JSContext
 
   SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
 }
 
 already_AddRefed<Promise> JSActor::SendQuery(JSContext* aCx,
                                              const nsAString& aMessageName,
                                              JS::Handle<JS::Value> aObj,
                                              ErrorResult& aRv) {
-  ipc::StructuredCloneData data;
+  Maybe<ipc::StructuredCloneData> data{std::in_place};
   if (!nsFrameMessageManager::GetParamsForMessage(
-          aCx, aObj, JS::UndefinedHandleValue, data)) {
+          aCx, aObj, JS::UndefinedHandleValue, *data)) {
     aRv.ThrowDataCloneError(nsPrintfCString(
         "Failed to serialize message '%s::%s'",
         NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
     return nullptr;
   }
 
   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
   if (NS_WARN_IF(!global)) {
@@ -342,18 +334,18 @@ void JSActor::ReceiveQueryReply(JSContex
   if (aMetadata.kind() == JSActorMessageKind::QueryResolve) {
     promise->MaybeResolve(data);
   } else {
     promise->MaybeReject(data);
   }
 }
 
 void JSActor::SendRawMessageInProcess(const JSActorMessageMeta& aMeta,
-                                      ipc::StructuredCloneData&& aData,
-                                      ipc::StructuredCloneData&& aStack,
+                                      Maybe<ipc::StructuredCloneData>&& aData,
+                                      Maybe<ipc::StructuredCloneData>&& aStack,
                                       OtherSideCallback&& aGetOtherSide) {
   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
   NS_DispatchToMainThread(NS_NewRunnableFunction(
       "JSActor Async Message",
       [aMeta, data{std::move(aData)}, stack{std::move(aStack)},
        getOtherSide{std::move(aGetOtherSide)}]() mutable {
         if (RefPtr<JSActorManager> otherSide = getOtherSide()) {
           otherSide->ReceiveRawMessage(aMeta, std::move(data),
@@ -395,40 +387,41 @@ void JSActor::QueryHandler::RejectedCall
       } else {
         JS_ClearPendingException(aCx);
       }
     } else {
       JS_ClearPendingException(aCx);
     }
   }
 
-  auto onerror = [&]() {
+  Maybe<ipc::StructuredCloneData> data = TryClone(aCx, value);
+  if (!data) {
     // Failed to clone the rejection value. 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.
     if (!JS::CallOriginalPromiseReject(aCx, aValue)) {
       JS_ClearPendingException(aCx);
     }
-  };
-  SendReply(aCx, JSActorMessageKind::QueryReject,
-            CloneOrUndefined(aCx, value, onerror));
+  }
+
+  SendReply(aCx, JSActorMessageKind::QueryReject, std::move(data));
 }
 
 void JSActor::QueryHandler::ResolvedCallback(JSContext* aCx,
                                              JS::Handle<JS::Value> aValue) {
   if (!mActor) {
     return;
   }
 
-  ipc::StructuredCloneData data;
-  data.InitScope(JS::StructuredCloneScope::DifferentProcess);
+  Maybe<ipc::StructuredCloneData> data{std::in_place};
+  data->InitScope(JS::StructuredCloneScope::DifferentProcess);
 
   IgnoredErrorResult error;
-  data.Write(aCx, aValue, error);
+  data->Write(aCx, aValue, error);
   if (NS_WARN_IF(error.Failed())) {
     JS_ClearPendingException(aCx);
 
     nsAutoCString msg;
     msg.Append(mActor->Name());
     msg.Append(':');
     msg.Append(NS_LossyConvertUTF16toASCII(mMessageName));
     msg.AppendLiteral(": message reply cannot be cloned.");
@@ -444,17 +437,17 @@ void JSActor::QueryHandler::ResolvedCall
     }
     return;
   }
 
   SendReply(aCx, JSActorMessageKind::QueryResolve, std::move(data));
 }
 
 void JSActor::QueryHandler::SendReply(JSContext* aCx, JSActorMessageKind aKind,
-                                      ipc::StructuredCloneData&& aData) {
+                                      Maybe<ipc::StructuredCloneData>&& aData) {
   MOZ_ASSERT(mActor);
 
   JSActorMessageMeta meta;
   meta.actorName() = mActor->Name();
   meta.messageName() = mMessageName;
   meta.queryId() = mQueryId;
   meta.kind() = aKind;
 
--- a/dom/ipc/jsactor/JSActor.h
+++ b/dom/ipc/jsactor/JSActor.h
@@ -55,30 +55,30 @@ class JSActor : public nsISupports, publ
 
   nsIGlobalObject* GetParentObject() const { return mGlobal; };
 
  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 JSActorMessageMeta& aMetadata,
-                              ipc::StructuredCloneData&& aData,
-                              ipc::StructuredCloneData&& aStack,
+                              Maybe<ipc::StructuredCloneData>&& aData,
+                              Maybe<ipc::StructuredCloneData>&& aStack,
                               ErrorResult& aRv) = 0;
 
   // Check if a message is so large that IPC will probably crash if we try to
   // send it. If it is too large, record telemetry about the message.
   static bool AllowMessage(const JSActorMessageMeta& aMetadata,
                            size_t aDataLength);
 
   // Helper method to send an in-process raw message.
   using OtherSideCallback = std::function<already_AddRefed<JSActorManager>()>;
   static void SendRawMessageInProcess(const JSActorMessageMeta& aMeta,
-                                      ipc::StructuredCloneData&& aData,
-                                      ipc::StructuredCloneData&& aStack,
+                                      Maybe<ipc::StructuredCloneData>&& aData,
+                                      Maybe<ipc::StructuredCloneData>&& aStack,
                                       OtherSideCallback&& aGetOtherSide);
 
   virtual ~JSActor() = default;
 
   void SetName(const nsACString& aName);
 
   bool CanSend() const { return mCanSend; }
 
@@ -126,17 +126,17 @@ class JSActor : public nsISupports, publ
 
     void ResolvedCallback(JSContext* aCx,
                           JS::Handle<JS::Value> aValue) override;
 
    private:
     ~QueryHandler() = default;
 
     void SendReply(JSContext* aCx, JSActorMessageKind aKind,
-                   ipc::StructuredCloneData&& aData);
+                   Maybe<ipc::StructuredCloneData>&& aData);
 
     RefPtr<JSActor> mActor;
     RefPtr<Promise> mPromise;
     nsString mMessageName;
     uint64_t mQueryId;
   };
 
   // A query which hasn't been resolved yet, along with metadata about what
--- a/dom/ipc/jsactor/JSActorManager.cpp
+++ b/dom/ipc/jsactor/JSActorManager.cpp
@@ -102,19 +102,20 @@ already_AddRefed<JSActor> JSActorManager
   do {                                     \
     if (XRE_IsParentProcess()) {           \
       MOZ_ASSERT(test, msg);               \
     } else {                               \
       MOZ_DIAGNOSTIC_ASSERT(test, msg);    \
     }                                      \
   } while (0)
 
-void JSActorManager::ReceiveRawMessage(const JSActorMessageMeta& aMetadata,
-                                       ipc::StructuredCloneData&& aData,
-                                       ipc::StructuredCloneData&& aStack) {
+void JSActorManager::ReceiveRawMessage(
+    const JSActorMessageMeta& aMetadata,
+    Maybe<ipc::StructuredCloneData>&& aData,
+    Maybe<ipc::StructuredCloneData>&& aStack) {
   MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
 
   CrashReporter::AutoAnnotateCrashReport autoActorName(
       CrashReporter::Annotation::JSActorName, aMetadata.actorName());
   CrashReporter::AutoAnnotateCrashReport autoMessageName(
       CrashReporter::Annotation::JSActorMessage,
       NS_LossyConvertUTF16toASCII(aMetadata.messageName()));
 
@@ -129,19 +130,23 @@ void JSActorManager::ReceiveRawMessage(c
   auto autoSetException =
       MakeScopeExit([&] { Unused << error.MaybeSetPendingException(cx); });
 
   // If an async stack was provided, set up our async stack state.
   JS::Rooted<JSObject*> stack(cx);
   Maybe<JS::AutoSetAsyncStackForNewCalls> stackSetter;
   {
     JS::Rooted<JS::Value> stackVal(cx);
-    aStack.Read(cx, &stackVal, error);
-    if (error.Failed()) {
-      return;
+    if (aStack) {
+      aStack->Read(cx, &stackVal, error);
+      if (error.Failed()) {
+        error.SuppressException();
+        JS_ClearPendingException(cx);
+        stackVal.setUndefined();
+      }
     }
 
     if (stackVal.isObject()) {
       stack = &stackVal.toObject();
       if (!js::IsSavedFrame(stack)) {
         CHILD_DIAGNOSTIC_ASSERT(false, "Stack must be a SavedFrame object");
         error.ThrowDataError("Actor async stack must be a SavedFrame object");
         return;
@@ -151,20 +156,22 @@ void JSActorManager::ReceiveRawMessage(c
   }
 
   RefPtr<JSActor> actor = GetActor(cx, aMetadata.actorName(), error);
   if (error.Failed()) {
     return;
   }
 
   JS::Rooted<JS::Value> data(cx);
-  aData.Read(cx, &data, error);
-  if (error.Failed()) {
-    CHILD_DIAGNOSTIC_ASSERT(false, "Should not receive non-decodable data");
-    return;
+  if (aData) {
+    aData->Read(cx, &data, error);
+    if (error.Failed()) {
+      CHILD_DIAGNOSTIC_ASSERT(false, "Should not receive non-decodable data");
+      return;
+    }
   }
 
   switch (aMetadata.kind()) {
     case JSActorMessageKind::QueryResolve:
     case JSActorMessageKind::QueryReject:
       actor->ReceiveQueryReply(cx, aMetadata, data, error);
       break;
 
--- a/dom/ipc/jsactor/JSActorManager.h
+++ b/dom/ipc/jsactor/JSActorManager.h
@@ -31,18 +31,18 @@ class JSActorManager : public nsISupport
    */
   already_AddRefed<JSActor> GetActor(JSContext* aCx, const nsACString& aName,
                                      ErrorResult& aRv);
 
   /**
    * Handle receiving a raw message from the other side.
    */
   void ReceiveRawMessage(const JSActorMessageMeta& aMetadata,
-                         ipc::StructuredCloneData&& aData,
-                         ipc::StructuredCloneData&& aStack);
+                         Maybe<ipc::StructuredCloneData>&& aData,
+                         Maybe<ipc::StructuredCloneData>&& aStack);
 
  protected:
   /**
    * Lifecycle methods which will fire the `willDestroy` and `didDestroy`
    * methods on relevant actors.
    */
   void JSActorWillDestroy();
   void JSActorDidDestroy();
--- a/dom/ipc/jsactor/JSProcessActorChild.cpp
+++ b/dom/ipc/jsactor/JSProcessActorChild.cpp
@@ -23,27 +23,32 @@ NS_INTERFACE_MAP_END_INHERITING(JSActor)
 NS_IMPL_ADDREF_INHERITED(JSProcessActorChild, JSActor)
 NS_IMPL_RELEASE_INHERITED(JSProcessActorChild, JSActor)
 
 JSObject* JSProcessActorChild::WrapObject(JSContext* aCx,
                                           JS::Handle<JSObject*> aGivenProto) {
   return JSProcessActorChild_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-void JSProcessActorChild::SendRawMessage(const JSActorMessageMeta& aMeta,
-                                         ipc::StructuredCloneData&& aData,
-                                         ipc::StructuredCloneData&& aStack,
-                                         ErrorResult& aRv) {
+void JSProcessActorChild::SendRawMessage(
+    const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+    Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
   if (NS_WARN_IF(!CanSend() || !mManager || !mManager->GetCanSend())) {
     aRv.ThrowInvalidStateError("JSProcessActorChild cannot send at the moment");
     return;
   }
 
-  if (NS_WARN_IF(
-          !AllowMessage(aMeta, aData.DataLength() + aStack.DataLength()))) {
+  size_t length = 0;
+  if (aData) {
+    length += aData->DataLength();
+  }
+  if (aStack) {
+    length += aStack->DataLength();
+  }
+  if (NS_WARN_IF(!AllowMessage(aMeta, length))) {
     aRv.ThrowDataCloneError(
         nsPrintfCString("JSProcessActorChild serialization error: data too "
                         "large, in actor '%s'",
                         PromiseFlatCString(aMeta.actorName()).get()));
     return;
   }
 
   // If the parent side is in the same process, we have a PInProcess manager,
@@ -51,26 +56,36 @@ void JSProcessActorChild::SendRawMessage
   ContentChild* contentChild = mManager->AsContentChild();
   if (!contentChild) {
     SendRawMessageInProcess(aMeta, std::move(aData), std::move(aStack), []() {
       return do_AddRef(InProcessParent::Singleton());
     });
     return;
   }
 
-  ClonedMessageData msgData;
-  ClonedMessageData stackData;
-  if (NS_WARN_IF(
-          !aData.BuildClonedMessageDataForChild(contentChild, msgData)) ||
-      NS_WARN_IF(
-          !aStack.BuildClonedMessageDataForChild(contentChild, stackData))) {
-    aRv.ThrowDataCloneError(nsPrintfCString(
-        "JSProcessActorChild serialization error: cannot clone, in actor '%s'",
-        PromiseFlatCString(aMeta.actorName()).get()));
-    return;
+  // Cross-process case - send data over ContentChild to other side.
+  Maybe<ClonedMessageData> msgData;
+  if (aData) {
+    msgData.emplace();
+    if (NS_WARN_IF(
+            !aData->BuildClonedMessageDataForChild(contentChild, *msgData))) {
+      aRv.ThrowDataCloneError(
+          nsPrintfCString("JSProcessActorChild serialization error: cannot "
+                          "clone, in actor '%s'",
+                          PromiseFlatCString(aMeta.actorName()).get()));
+      return;
+    }
+  }
+
+  Maybe<ClonedMessageData> stackData;
+  if (aStack) {
+    stackData.emplace();
+    if (!aStack->BuildClonedMessageDataForChild(contentChild, *stackData)) {
+      stackData.reset();
+    }
   }
 
   if (NS_WARN_IF(!contentChild->SendRawMessage(aMeta, msgData, stackData))) {
     aRv.ThrowOperationError(
         nsPrintfCString("JSProcessActorChild send error in actor '%s'",
                         PromiseFlatCString(aMeta.actorName()).get()));
     return;
   }
--- a/dom/ipc/jsactor/JSProcessActorChild.h
+++ b/dom/ipc/jsactor/JSProcessActorChild.h
@@ -36,18 +36,18 @@ class JSProcessActorChild final : public
   void Init(const nsACString& aName, nsIDOMProcessChild* aManager);
   void ClearManager() override;
 
  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 JSActorMessageMeta& aMetadata,
-                              ipc::StructuredCloneData&& aData,
-                              ipc::StructuredCloneData&& aStack,
+                              Maybe<ipc::StructuredCloneData>&& aData,
+                              Maybe<ipc::StructuredCloneData>&& aStack,
                               ErrorResult& aRv) override;
 
  private:
   ~JSProcessActorChild() { MOZ_ASSERT(!mManager); }
 
   nsCOMPtr<nsIDOMProcessChild> mManager;
 };
 
--- a/dom/ipc/jsactor/JSProcessActorParent.cpp
+++ b/dom/ipc/jsactor/JSProcessActorParent.cpp
@@ -34,30 +34,35 @@ void JSProcessActorParent::Init(const ns
   SetName(aName);
   mManager = aManager;
 
   InvokeCallback(CallbackFunction::ActorCreated);
 }
 
 JSProcessActorParent::~JSProcessActorParent() { MOZ_ASSERT(!mManager); }
 
-void JSProcessActorParent::SendRawMessage(const JSActorMessageMeta& aMeta,
-                                          ipc::StructuredCloneData&& aData,
-                                          ipc::StructuredCloneData&& aStack,
-                                          ErrorResult& aRv) {
+void JSProcessActorParent::SendRawMessage(
+    const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+    Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
   if (NS_WARN_IF(!CanSend() || !mManager || !mManager->GetCanSend())) {
     aRv.ThrowInvalidStateError(
         nsPrintfCString("Actor '%s' cannot send message '%s' during shutdown.",
                         PromiseFlatCString(aMeta.actorName()).get(),
                         NS_ConvertUTF16toUTF8(aMeta.messageName()).get()));
     return;
   }
 
-  if (NS_WARN_IF(
-          !AllowMessage(aMeta, aData.DataLength() + aStack.DataLength()))) {
+  size_t length = 0;
+  if (aData) {
+    length += aData->DataLength();
+  }
+  if (aStack) {
+    length += aStack->DataLength();
+  }
+  if (NS_WARN_IF(!AllowMessage(aMeta, length))) {
     aRv.ThrowDataError(nsPrintfCString(
         "Actor '%s' cannot send message '%s': message too long.",
         PromiseFlatCString(aMeta.actorName()).get(),
         NS_ConvertUTF16toUTF8(aMeta.messageName()).get()));
     return;
   }
 
   // If the parent side is in the same process, we have a PInProcess manager,
@@ -66,31 +71,41 @@ void JSProcessActorParent::SendRawMessag
   if (!contentParent) {
     SendRawMessageInProcess(aMeta, std::move(aData), std::move(aStack), []() {
       return do_AddRef(InProcessChild::Singleton());
     });
     return;
   }
 
   // Cross-process case - send data over ContentParent to other side.
-  ClonedMessageData msgData;
-  ClonedMessageData stackData;
-  if (NS_WARN_IF(
-          !aData.BuildClonedMessageDataForParent(contentParent, msgData)) ||
-      NS_WARN_IF(
-          !aStack.BuildClonedMessageDataForParent(contentParent, stackData))) {
-    aRv.ThrowDataCloneError(
-        nsPrintfCString("Actor '%s' cannot send message '%s': cannot clone.",
-                        PromiseFlatCString(aMeta.actorName()).get(),
-                        NS_ConvertUTF16toUTF8(aMeta.messageName()).get()));
-    return;
+  Maybe<ClonedMessageData> msgData;
+  if (aData) {
+    msgData.emplace();
+    if (NS_WARN_IF(
+            !aData->BuildClonedMessageDataForParent(contentParent, *msgData))) {
+      aRv.ThrowDataCloneError(
+          nsPrintfCString("Actor '%s' cannot send message '%s': cannot clone.",
+                          PromiseFlatCString(aMeta.actorName()).get(),
+                          NS_ConvertUTF16toUTF8(aMeta.messageName()).get()));
+      return;
+    }
+  }
+
+  Maybe<ClonedMessageData> stackData;
+  if (aStack) {
+    stackData.emplace();
+    if (!aStack->BuildClonedMessageDataForParent(contentParent, *stackData)) {
+      stackData.reset();
+    }
   }
 
   if (NS_WARN_IF(!contentParent->SendRawMessage(aMeta, msgData, stackData))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+    aRv.ThrowOperationError(
+        nsPrintfCString("JSProcessActorParent send error in actor '%s'",
+                        PromiseFlatCString(aMeta.actorName()).get()));
     return;
   }
 }
 
 void JSProcessActorParent::ClearManager() { mManager = nullptr; }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/jsactor/JSProcessActorParent.h
+++ b/dom/ipc/jsactor/JSProcessActorParent.h
@@ -43,18 +43,18 @@ class JSProcessActorParent final : publi
   void Init(const nsACString& aName, nsIDOMProcessParent* aManager);
   void ClearManager() override;
 
  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 JSActorMessageMeta& aMetadata,
-                              ipc::StructuredCloneData&& aData,
-                              ipc::StructuredCloneData&& aStack,
+                              Maybe<ipc::StructuredCloneData>&& aData,
+                              Maybe<ipc::StructuredCloneData>&& aStack,
                               ErrorResult& aRv) override;
 
  private:
   ~JSProcessActorParent();
 
   nsCOMPtr<nsIDOMProcessParent> mManager;
 };
 
--- a/dom/ipc/jsactor/JSWindowActorChild.cpp
+++ b/dom/ipc/jsactor/JSWindowActorChild.cpp
@@ -30,50 +30,73 @@ void JSWindowActorChild::Init(const nsAC
                               WindowGlobalChild* aManager) {
   MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorChild twice!");
   SetName(aName);
   mManager = aManager;
 
   InvokeCallback(CallbackFunction::ActorCreated);
 }
 
-void JSWindowActorChild::SendRawMessage(const JSActorMessageMeta& aMeta,
-                                        ipc::StructuredCloneData&& aData,
-                                        ipc::StructuredCloneData&& aStack,
-                                        ErrorResult& aRv) {
+void JSWindowActorChild::SendRawMessage(
+    const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+    Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
   if (NS_WARN_IF(!CanSend() || !mManager || !mManager->CanSend())) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aRv.ThrowInvalidStateError("JSWindowActorChild cannot send at the moment");
     return;
   }
 
   if (mManager->IsInProcess()) {
     SendRawMessageInProcess(
         aMeta, std::move(aData), std::move(aStack),
         [manager{mManager}]() { return manager->GetParentActor(); });
     return;
   }
 
-  if (NS_WARN_IF(
-          !AllowMessage(aMeta, aData.DataLength() + aStack.DataLength()))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+  size_t length = 0;
+  if (aData) {
+    length += aData->DataLength();
+  }
+  if (aStack) {
+    length += aStack->DataLength();
+  }
+
+  if (NS_WARN_IF(!AllowMessage(aMeta, length))) {
+    aRv.ThrowDataCloneError(
+        nsPrintfCString("JSWindowActorChild serialization error: data too "
+                        "large, in actor '%s'",
+                        PromiseFlatCString(aMeta.actorName()).get()));
     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)) ||
-      NS_WARN_IF(!aStack.BuildClonedMessageDataForChild(cc, stackData))) {
-    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
-    return;
+  Maybe<ClonedMessageData> msgData;
+  if (aData) {
+    msgData.emplace();
+    if (NS_WARN_IF(!aData->BuildClonedMessageDataForChild(cc, *msgData))) {
+      aRv.ThrowDataCloneError(
+          nsPrintfCString("JSWindowActorChild serialization error: cannot "
+                          "clone, in actor '%s'",
+                          PromiseFlatCString(aMeta.actorName()).get()));
+      return;
+    }
+  }
+
+  Maybe<ClonedMessageData> stackData;
+  if (aStack) {
+    stackData.emplace();
+    if (!aStack->BuildClonedMessageDataForChild(cc, *stackData)) {
+      stackData.reset();
+    }
   }
 
   if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData, stackData))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+    aRv.ThrowOperationError(
+        nsPrintfCString("JSWindowActorChild send error in actor '%s'",
+                        PromiseFlatCString(aMeta.actorName()).get()));
     return;
   }
 }
 
 Document* JSWindowActorChild::GetDocument(ErrorResult& aRv) {
   if (!mManager) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
--- a/dom/ipc/jsactor/JSWindowActorChild.h
+++ b/dom/ipc/jsactor/JSWindowActorChild.h
@@ -55,18 +55,18 @@ class JSWindowActorChild final : public 
   void ClearManager() override;
   Document* GetDocument(ErrorResult& aRv);
   BrowsingContext* GetBrowsingContext(ErrorResult& aRv);
   nsIDocShell* GetDocShell(ErrorResult& aRv);
   Nullable<WindowProxyHolder> GetContentWindow(ErrorResult& aRv);
 
  protected:
   void SendRawMessage(const JSActorMessageMeta& aMeta,
-                      ipc::StructuredCloneData&& aData,
-                      ipc::StructuredCloneData&& aStack,
+                      Maybe<ipc::StructuredCloneData>&& aData,
+                      Maybe<ipc::StructuredCloneData>&& aStack,
                       ErrorResult& aRv) override;
 
  private:
   ~JSWindowActorChild();
 
   RefPtr<WindowGlobalChild> mManager;
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
--- a/dom/ipc/jsactor/JSWindowActorParent.cpp
+++ b/dom/ipc/jsactor/JSWindowActorParent.cpp
@@ -27,83 +27,75 @@ void JSWindowActorParent::Init(const nsA
                                WindowGlobalParent* aManager) {
   MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorParent twice!");
   SetName(aName);
   mManager = aManager;
 
   InvokeCallback(CallbackFunction::ActorCreated);
 }
 
-namespace {
-
-class AsyncMessageToChild : public Runnable {
- public:
-  AsyncMessageToChild(const JSActorMessageMeta& 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), std::move(mStack));
-    }
-    return NS_OK;
-  }
-
- private:
-  JSActorMessageMeta mMetadata;
-  ipc::StructuredCloneData mData;
-  ipc::StructuredCloneData mStack;
-  RefPtr<WindowGlobalParent> mManager;
-};
-
-}  // anonymous namespace
-
-void JSWindowActorParent::SendRawMessage(const JSActorMessageMeta& aMeta,
-                                         ipc::StructuredCloneData&& aData,
-                                         ipc::StructuredCloneData&& aStack,
-                                         ErrorResult& aRv) {
+void JSWindowActorParent::SendRawMessage(
+    const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+    Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
   if (NS_WARN_IF(!CanSend() || !mManager || !mManager->CanSend())) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aRv.ThrowInvalidStateError("JSWindowActorParent cannot send at the moment");
     return;
   }
 
   if (mManager->IsInProcess()) {
     SendRawMessageInProcess(
         aMeta, std::move(aData), std::move(aStack),
         [manager{mManager}]() { return manager->GetChildActor(); });
     return;
   }
 
-  if (NS_WARN_IF(
-          !AllowMessage(aMeta, aData.DataLength() + aStack.DataLength()))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+  size_t length = 0;
+  if (aData) {
+    length += aData->DataLength();
+  }
+  if (aStack) {
+    length += aStack->DataLength();
+  }
+
+  if (NS_WARN_IF(!AllowMessage(aMeta, length))) {
+    aRv.ThrowDataCloneError(
+        nsPrintfCString("JSWindowActorParent serialization error: data too "
+                        "large, in actor '%s'",
+                        PromiseFlatCString(aMeta.actorName()).get()));
     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)) ||
-      NS_WARN_IF(!aStack.BuildClonedMessageDataForParent(cp, stackData))) {
-    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
-    return;
+
+  Maybe<ClonedMessageData> msgData;
+  if (aData) {
+    msgData.emplace();
+    if (NS_WARN_IF(!aData->BuildClonedMessageDataForParent(cp, *msgData))) {
+      aRv.ThrowDataCloneError(
+          nsPrintfCString("JSWindowActorParent serialization error: cannot "
+                          "clone, in actor '%s'",
+                          PromiseFlatCString(aMeta.actorName()).get()));
+      return;
+    }
+  }
+
+  Maybe<ClonedMessageData> stackData;
+  if (aStack) {
+    stackData.emplace();
+    if (!aStack->BuildClonedMessageDataForParent(cp, *stackData)) {
+      stackData.reset();
+    }
   }
 
   if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData, stackData))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+    aRv.ThrowOperationError(
+        nsPrintfCString("JSWindowActorParent send error in actor '%s'",
+                        PromiseFlatCString(aMeta.actorName()).get()));
     return;
   }
 }
 
 CanonicalBrowsingContext* JSWindowActorParent::GetBrowsingContext(
     ErrorResult& aRv) {
   if (!mManager) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
--- a/dom/ipc/jsactor/JSWindowActorParent.h
+++ b/dom/ipc/jsactor/JSWindowActorParent.h
@@ -46,18 +46,18 @@ class JSWindowActorParent final : public
 
   WindowGlobalParent* GetManager() const;
   void Init(const nsACString& aName, WindowGlobalParent* aManager);
   void ClearManager() override;
   CanonicalBrowsingContext* GetBrowsingContext(ErrorResult& aRv);
 
  protected:
   void SendRawMessage(const JSActorMessageMeta& aMeta,
-                      ipc::StructuredCloneData&& aData,
-                      ipc::StructuredCloneData&& aStack,
+                      Maybe<ipc::StructuredCloneData>&& aData,
+                      Maybe<ipc::StructuredCloneData>&& aStack,
                       ErrorResult& aRv) override;
 
  private:
   ~JSWindowActorParent();
 
   RefPtr<WindowGlobalParent> mManager;
 };