Bug 1456986 Make ServiceWorker::Inner::PostMessage() use StructuredCloneData. r=baku
authorBen Kelly <ben@wanderview.com>
Fri, 04 May 2018 06:38:52 -0700
changeset 473017 509957c7a9f98e5bc5fda274eb958d7b14294444
parent 473016 5bc4513eef701a4874bffc984868a15f485a24b4
child 473018 4832928b4bb4153a0eedb9650354982fb21e4b2d
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1456986
milestone61.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 1456986 Make ServiceWorker::Inner::PostMessage() use StructuredCloneData. r=baku
dom/serviceworkers/ServiceWorker.cpp
dom/serviceworkers/ServiceWorker.h
dom/serviceworkers/ServiceWorkerInfo.cpp
dom/serviceworkers/ServiceWorkerInfo.h
dom/serviceworkers/ServiceWorkerPrivate.cpp
dom/serviceworkers/ServiceWorkerPrivate.h
--- a/dom/serviceworkers/ServiceWorker.cpp
+++ b/dom/serviceworkers/ServiceWorker.cpp
@@ -12,16 +12,17 @@
 #include "ServiceWorkerPrivate.h"
 
 #include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/ClientIPCTypes.h"
 #include "mozilla/dom/ClientState.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
 
 #ifdef XP_WIN
 #undef PostMessage
 #endif
 
 using mozilla::ErrorResult;
 using namespace mozilla::dom;
 
@@ -142,17 +143,52 @@ ServiceWorker::PostMessage(JSContext* aC
                            const Sequence<JSObject*>& aTransferable,
                            ErrorResult& aRv)
 {
   if (State() == ServiceWorkerState::Redundant) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  mInner->PostMessage(GetParentObject(), aCx, aMessage, aTransferable, aRv);
+  nsPIDOMWindowInner* window = GetOwner();
+  if (NS_WARN_IF(!window || !window->GetExtantDoc())) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
+  if (storageAllowed != nsContentUtils::StorageAccess::eAllow) {
+    ServiceWorkerManager::LocalizeAndReportToAllClients(
+      mDescriptor.Scope(), "ServiceWorkerPostMessageStorageError",
+      nsTArray<nsString> { NS_ConvertUTF8toUTF16(mDescriptor.Scope()) });
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
+  Maybe<ClientInfo> clientInfo = window->GetClientInfo();
+  Maybe<ClientState> clientState = window->GetClientState();
+  if (NS_WARN_IF(clientInfo.isNothing() || clientState.isNothing())) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+  aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+                                                          &transferable);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  ipc::StructuredCloneData data;
+  data.Write(aCx, aMessage, transferable, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  mInner->PostMessage(Move(data), clientInfo.ref(), clientState.ref());
 }
 
 
 const ServiceWorkerDescriptor&
 ServiceWorker::Descriptor() const
 {
   return mDescriptor;
 }
--- a/dom/serviceworkers/ServiceWorker.h
+++ b/dom/serviceworkers/ServiceWorker.h
@@ -15,16 +15,20 @@
 #undef PostMessage
 #endif
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
+namespace ipc {
+class StructuredCloneData;
+} // namespace ipc
+
 #define NS_DOM_SERVICEWORKER_IID \
   {0xd42e0611, 0x3647, 0x4319, {0xae, 0x05, 0x19, 0x89, 0x59, 0xba, 0x99, 0x5e}}
 
 bool
 ServiceWorkerVisible(JSContext* aCx, JSObject* aObj);
 
 class ServiceWorker final : public DOMEventTargetHelper
 {
@@ -49,20 +53,19 @@ public:
     AddServiceWorker(ServiceWorker* aWorker) = 0;
 
     // This is called when the DOM ServiceWorker object is
     // destroyed and drops its ref to the Inner object.
     virtual void
     RemoveServiceWorker(ServiceWorker* aWorker) = 0;
 
     virtual void
-    PostMessage(nsIGlobalObject* aGlobal,
-                JSContext* aCx, JS::Handle<JS::Value> aMessage,
-                const Sequence<JSObject*>& aTransferable,
-                ErrorResult& aRv) = 0;
+    PostMessage(ipc::StructuredCloneData&& aData,
+                const ClientInfo& aClientInfo,
+                const ClientState& aClientState) = 0;
 
     NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
   };
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_SERVICEWORKER_IID)
   NS_DECL_ISUPPORTS_INHERITED
 
   IMPL_EVENT_HANDLER(statechange)
--- a/dom/serviceworkers/ServiceWorkerInfo.cpp
+++ b/dom/serviceworkers/ServiceWorkerInfo.cpp
@@ -251,48 +251,23 @@ ServiceWorkerInfo::RemoveServiceWorker(S
     workerURL.Equals(NS_ConvertUTF8toUTF16(mDescriptor.ScriptURL())));
 #endif
 
   DebugOnly<bool> removed = mInstances.RemoveElement(aWorker);
   MOZ_ASSERT(removed);
 }
 
 void
-ServiceWorkerInfo::PostMessage(nsIGlobalObject* aGlobal,
-                               JSContext* aCx, JS::Handle<JS::Value> aMessage,
-                               const Sequence<JSObject*>& aTransferable,
-                               ErrorResult& aRv)
+ServiceWorkerInfo::PostMessage(ipc::StructuredCloneData&& aData,
+                               const ClientInfo& aClientInfo,
+                               const ClientState& aClientState)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
-  if (NS_WARN_IF(!window || !window->GetExtantDoc())) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-
-  auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
-  if (storageAllowed != nsContentUtils::StorageAccess::eAllow) {
-    ServiceWorkerManager::LocalizeAndReportToAllClients(
-      Scope(), "ServiceWorkerPostMessageStorageError",
-      nsTArray<nsString> { NS_ConvertUTF8toUTF16(Scope()) });
-    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return;
-  }
-
-  Maybe<ClientInfo> clientInfo = window->GetClientInfo();
-  Maybe<ClientState> clientState = window->GetClientState();
-  if (NS_WARN_IF(clientInfo.isNothing() || clientState.isNothing())) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-
-  aRv = mServiceWorkerPrivate->SendMessageEvent(aCx, aMessage, aTransferable,
-                                                ClientInfoAndState(clientInfo.ref().ToIPC(),
-                                                                   clientState.ref().ToIPC()));
+  mServiceWorkerPrivate->SendMessageEvent(Move(aData),
+                                          ClientInfoAndState(aClientInfo.ToIPC(),
+                                                             aClientState.ToIPC()));
 }
 
 void
 ServiceWorkerInfo::UpdateInstalledTime()
 {
   MOZ_ASSERT(State() == ServiceWorkerState::Installed);
   MOZ_ASSERT(mInstalledTime == 0);
 
--- a/dom/serviceworkers/ServiceWorkerInfo.h
+++ b/dom/serviceworkers/ServiceWorkerInfo.h
@@ -12,16 +12,17 @@
 #include "mozilla/dom/WorkerCommon.h"
 #include "mozilla/OriginAttributes.h"
 #include "nsIServiceWorkerManager.h"
 #include "ServiceWorker.h"
 
 namespace mozilla {
 namespace dom {
 
+class ClientInfoAndState;
 class ServiceWorkerPrivate;
 
 /*
  * Wherever the spec treats a worker instance and a description of said worker
  * as the same thing; i.e. "Resolve foo with
  * _GetNewestWorker(serviceWorkerRegistration)", we represent the description
  * by this class and spawn a ServiceWorker in the right global when required.
  */
@@ -80,20 +81,19 @@ private:
   // ServiceWorker::Inner implementation
   virtual void
   AddServiceWorker(ServiceWorker* aWorker) override;
 
   virtual void
   RemoveServiceWorker(ServiceWorker* aWorker) override;
 
   virtual void
-  PostMessage(nsIGlobalObject* aGlobal,
-              JSContext* aCx, JS::Handle<JS::Value> aMessage,
-              const Sequence<JSObject*>& aTransferable,
-              ErrorResult& aRv) override;
+  PostMessage(ipc::StructuredCloneData&& aData,
+              const ClientInfo& aClientInfo,
+              const ClientState& aClientState) override;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISERVICEWORKERINFO
 
   class ServiceWorkerPrivate*
   WorkerPrivate() const
   {
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -18,30 +18,33 @@
 #include "nsITimedChannel.h"
 #include "nsIUploadChannel2.h"
 #include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "nsQueryObject.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/JSObjectHolder.h"
 #include "mozilla/dom/Client.h"
 #include "mozilla/dom/ClientIPCTypes.h"
 #include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/IndexedDatabaseManager.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/NotificationEvent.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/PushEventBinding.h"
 #include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/WorkerDebugger.h"
 #include "mozilla/dom/WorkerRef.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/Unused.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
 namespace dom {
 
@@ -207,16 +210,34 @@ ServiceWorkerPrivate::CheckScriptEvaluat
                                                                    aScriptEvaluationCallback);
   if (NS_WARN_IF(!r->Dispatch())) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+JSObject*
+ServiceWorkerPrivate::GetOrCreateSandbox(JSContext* aCx)
+{
+  AssertIsOnMainThread();
+
+  if (!mSandbox) {
+    nsIXPConnect* xpc = nsContentUtils::XPConnect();
+
+    JS::Rooted<JSObject*> sandbox(aCx);
+    nsresult rv = xpc->CreateSandbox(aCx, mInfo->Principal(), sandbox.address());
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    mSandbox = new JSObjectHolder(aCx, sandbox);
+  }
+
+  return mSandbox->GetJSObject();
+}
+
 namespace {
 
 enum ExtendableEventResult {
     Rejected = 0,
     Resolved
 };
 
 class ExtendableEventCallback {
@@ -495,45 +516,45 @@ public:
       return NS_ERROR_XPC_JS_THREW_EXCEPTION;
     }
 
     return NS_OK;
   }
 };
 
 class SendMessageEventRunnable final : public ExtendableEventWorkerRunnable
-                                     , public StructuredCloneHolder
 {
+  StructuredCloneHolder mData;
   const ClientInfoAndState mClientInfoAndState;
 
 public:
   SendMessageEventRunnable(WorkerPrivate*  aWorkerPrivate,
                            KeepAliveToken* aKeepAliveToken,
+                           StructuredCloneHolder&& aData,
                            const ClientInfoAndState& aClientInfoAndState)
     : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
-    , StructuredCloneHolder(CloningSupported, TransferringSupported,
-                            StructuredCloneScope::SameProcessDifferentThread)
+    , mData(Move(aData))
     , mClientInfoAndState(aClientInfoAndState)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     JS::Rooted<JS::Value> messageData(aCx);
     nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
     ErrorResult rv;
-    Read(sgo, aCx, &messageData, rv);
+    mData.Read(sgo, aCx, &messageData, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return true;
     }
 
     Sequence<OwningNonNull<MessagePort>> ports;
-    if (!TakeTransferredPortsAsSequence(ports)) {
+    if (!mData.TakeTransferredPortsAsSequence(ports)) {
       return true;
     }
 
     RootedDictionary<ExtendableMessageEventInit> init(aCx);
 
     init.mBubbles = false;
     init.mCancelable = false;
 
@@ -558,45 +579,92 @@ public:
                                                              extendableEvent,
                                                              nullptr));
   }
 };
 
 } // anonymous namespace
 
 nsresult
-ServiceWorkerPrivate::SendMessageEvent(JSContext* aCx,
-                                       JS::Handle<JS::Value> aMessage,
-                                       const Sequence<JSObject*>& aTransferable,
+ServiceWorkerPrivate::SendMessageEvent(ipc::StructuredCloneData&& aData,
                                        const ClientInfoAndState& aClientInfoAndState)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  ErrorResult rv(SpawnWorkerIfNeeded(MessageEvent));
-  if (NS_WARN_IF(rv.Failed())) {
+  ErrorResult rv;
+
+  // Ideally we would simply move the StructuredCloneData to the
+  // SendMessageEventRunnable, but we cannot because it uses non-threadsafe
+  // ref-counting.  The following gnarly code unpacks the IPC-friendly
+  // StructuredCloneData and re-packs it into the thread-friendly
+  // StructuredCloneHolder.  In the future we should remove this and make
+  // it easier to simple move the data to the other thread.  See bug 1458936.
+
+  AutoSafeJSContext cx;
+  JSObject* sandbox = GetOrCreateSandbox(cx);
+  NS_ENSURE_TRUE(sandbox, NS_ERROR_FAILURE);
+
+  JS::Rooted<JSObject*> global(cx, sandbox);
+  NS_ENSURE_TRUE(sandbox, NS_ERROR_FAILURE);
+
+  // The CreateSandbox call returns a proxy to the actual sandbox object.  We
+  // don't need a proxy here.
+  global = js::UncheckedUnwrap(global);
+
+  JSAutoCompartment ac(cx, global);
+
+  JS::Rooted<JS::Value> messageData(cx);
+  aData.Read(cx, &messageData, rv);
+  if (rv.Failed()) {
     return rv.StealNSResult();
   }
 
-  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedHandleValue);
+  Sequence<OwningNonNull<MessagePort>> ports;
+  if (!aData.TakeTransferredPortsAsSequence(ports)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, ports.Length()));
+  NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+  for (uint32_t i = 0; i < ports.Length(); ++i) {
+    JS::Rooted<JS::Value> value(cx);
+    if (!GetOrCreateDOMReflector(cx, ports[i], &value)) {
+      JS_ClearPendingException(cx);
+      return NS_ERROR_FAILURE;
+    }
 
-  rv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
-                                                         &transferable);
+    if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  JS::Rooted<JS::Value> transferable(cx);
+  transferable.setObject(*array);
+
+  StructuredCloneHolder holder(StructuredCloneHolder::CloningSupported,
+                               StructuredCloneHolder::TransferringSupported,
+                               JS::StructuredCloneScope::SameProcessDifferentThread);
+  holder.Write(cx, messageData, transferable, JS::CloneDataPolicy(), rv);
+  if (rv.Failed()) {
+    return rv.StealNSResult();
+  }
+
+  // Now that the re-packing is complete, send a runnable to the service worker
+  // thread.
+
+  rv = SpawnWorkerIfNeeded(MessageEvent);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
   RefPtr<SendMessageEventRunnable> runnable =
-    new SendMessageEventRunnable(mWorkerPrivate, token, aClientInfoAndState);
-
-  runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    return rv.StealNSResult();
-  }
-
+    new SendMessageEventRunnable(mWorkerPrivate, token, Move(holder),
+                                 aClientInfoAndState);
   if (!runnable->Dispatch()) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 namespace {
--- a/dom/serviceworkers/ServiceWorkerPrivate.h
+++ b/dom/serviceworkers/ServiceWorkerPrivate.h
@@ -11,23 +11,30 @@
 #include "mozilla/dom/WorkerPrivate.h"
 
 #define NOTIFICATION_CLICK_EVENT_NAME "notificationclick"
 #define NOTIFICATION_CLOSE_EVENT_NAME "notificationclose"
 
 class nsIInterceptedChannel;
 
 namespace mozilla {
+
+class JSObjectHolder;
+
 namespace dom {
 
 class ClientInfoAndState;
 class KeepAliveToken;
 class ServiceWorkerInfo;
 class ServiceWorkerRegistrationInfo;
 
+namespace ipc {
+class StructuredCloneData;
+} // namespace ipc
+
 class LifeCycleEventCallback : public Runnable
 {
 public:
   LifeCycleEventCallback() : Runnable("dom::LifeCycleEventCallback") {}
 
   // Called on the worker thread.
   virtual void
   SetResult(bool aResult) = 0;
@@ -77,18 +84,17 @@ public:
 protected:
   nsCycleCollectingAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
 public:
   explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo);
 
   nsresult
-  SendMessageEvent(JSContext* aCx, JS::Handle<JS::Value> aMessage,
-                   const Sequence<JSObject*>& aTransferable,
+  SendMessageEvent(ipc::StructuredCloneData&& aData,
                    const ClientInfoAndState& aClientInfoAndState);
 
   // This is used to validate the worker script and continue the installation
   // process.
   nsresult
   CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
 
   nsresult
@@ -194,32 +200,40 @@ private:
                       bool* aNewWorkerCreated = nullptr,
                       nsILoadGroup* aLoadGroup = nullptr);
 
   ~ServiceWorkerPrivate();
 
   already_AddRefed<KeepAliveToken>
   CreateEventKeepAliveToken();
 
+  JSObject*
+  GetOrCreateSandbox(JSContext* aCx);
+
   // The info object owns us. It is possible to outlive it for a brief period
   // of time if there are pending waitUntil promises, in which case it
   // will be null and |SpawnWorkerIfNeeded| will always fail.
   ServiceWorkerInfo* MOZ_NON_OWNING_REF mInfo;
 
   // The WorkerPrivate object can only be closed by this class or by the
   // RuntimeService class if gecko is shutting down. Closing the worker
   // multiple times is OK, since the second attempt will be a no-op.
   RefPtr<WorkerPrivate> mWorkerPrivate;
 
   nsCOMPtr<nsITimer> mIdleWorkerTimer;
 
   // We keep a token for |dom.serviceWorkers.idle_timeout| seconds to give the
   // worker a grace period after each event.
   RefPtr<KeepAliveToken> mIdleKeepAliveToken;
 
+  // Sandbox global used to re-pack structured clone data before sending
+  // to the service worker thread.  Ideally we would remove this and just
+  // make StructuredCloneData thread safe enough to pass to the worker thread.
+  RefPtr<JSObjectHolder> mSandbox;
+
   uint64_t mDebuggerCount;
 
   uint64_t mTokenCount;
 
   // Meant for keeping objects alive while handling requests from the worker
   // on the main thread. Access to this array is provided through
   // |StoreISupports| and |RemoveISupports|. Note that the array is also
   // cleared whenever the worker is terminated.