Bug 1609916 - Callback from SC to DOM on SAB cloning. r=luke
☠☠ backed out by 62f4ddbaad18 ☠ ☠
authorLars T Hansen <lhansen@mozilla.com>
Fri, 07 Feb 2020 15:42:37 +0000
changeset 512906 1dc5c3074b57fb45d0a94188e69048da993360ca
parent 512905 ecb837a257f43805e27c5fc093391c490ac0fd27
child 512907 bef203601b7f9476176fd7da4110b82a331e3dd3
push id37101
push usernerli@mozilla.com
push dateSat, 08 Feb 2020 09:25:03 +0000
treeherdermozilla-central@cb56699431a0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1609916
milestone74.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 1609916 - Callback from SC to DOM on SAB cloning. r=luke When structured clone reads a SAB and creates a new SAB object, or when it serializes a SAB onto a channel, call a callback that lets the embedder know. The embedder can then adjust its policy. Concretely, we want to allow the browser to serialize threads in a process that uses JS shared memory. Note, for WebAssembly.Memory, reading and writing are delegated to the cloning operations for SAB, so no special handling is needed. Differential Revision: https://phabricator.services.mozilla.com/D61455
dom/base/StructuredCloneHolder.cpp
dom/indexedDB/IDBObjectStore.cpp
js/public/StructuredClone.h
js/src/vm/StructuredClone.cpp
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -138,17 +138,17 @@ void AssertTagValues() {
 }
 
 }  // anonymous namespace
 
 const JSStructuredCloneCallbacks StructuredCloneHolder::sCallbacks = {
     StructuredCloneCallbacksRead,          StructuredCloneCallbacksWrite,
     StructuredCloneCallbacksError,         StructuredCloneCallbacksReadTransfer,
     StructuredCloneCallbacksWriteTransfer, StructuredCloneCallbacksFreeTransfer,
-    StructuredCloneCallbacksCanTransfer,
+    StructuredCloneCallbacksCanTransfer,   nullptr,
 };
 
 // StructuredCloneHolderBase class
 
 StructuredCloneHolderBase::StructuredCloneHolderBase(
     StructuredCloneScope aScope)
     : mStructuredCloneScope(aScope)
 #ifdef DEBUG
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -414,23 +414,20 @@ bool CopyingStructuredCloneWriteCallback
   }
 
   return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter,
                                                               aObj);
 }
 
 nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) {
   static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = {
-      nullptr /* read */,
-      StructuredCloneWriteCallback /* write */,
-      nullptr /* reportError */,
-      nullptr /* readTransfer */,
-      nullptr /* writeTransfer */,
-      nullptr /* freeTransfer */,
-      nullptr /* canTransfer */
+      nullptr /* read */,          StructuredCloneWriteCallback /* write */,
+      nullptr /* reportError */,   nullptr /* readTransfer */,
+      nullptr /* writeTransfer */, nullptr /* freeTransfer */,
+      nullptr /* canTransfer */,   nullptr /* sabCloned */
   };
 
   MOZ_ASSERT(aCx);
 
   auto* const data = static_cast<GetAddInfoClosure*>(aClosure);
   MOZ_ASSERT(data);
 
   data->mCloneWriteInfo.mOffsetToKeyProp = 0;
@@ -1057,16 +1054,17 @@ bool IDBObjectStore::DeserializeValue(JS
 
   static const JSStructuredCloneCallbacks callbacks = {
       CommonStructuredCloneReadCallback,
       nullptr,
       nullptr,
       nullptr,
       nullptr,
       nullptr,
+      nullptr,
       nullptr};
 
   // FIXME: Consider to use StructuredCloneHolder here and in other
   //        deserializing methods.
   return JS_ReadStructuredClone(
       aCx, aCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
       JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
       JS::CloneDataPolicy(), &callbacks, &aCloneReadInfo);
@@ -1214,16 +1212,17 @@ class DeserializeIndexValueHelper final 
                                  JS::MutableHandle<JS::Value> aValue) {
     static const JSStructuredCloneCallbacks callbacks = {
         CommonStructuredCloneReadCallback,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
+        nullptr,
         nullptr};
 
     if (!JS_ReadStructuredClone(
             aCx, mCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
             JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
             JS::CloneDataPolicy(), &callbacks, &mCloneReadInfo)) {
       return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
@@ -1320,16 +1319,17 @@ class DeserializeUpgradeValueHelper fina
                                    JS::MutableHandle<JS::Value> aValue) {
     static const JSStructuredCloneCallbacks callbacks = {
         CommonStructuredCloneReadCallback,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
+        nullptr,
         nullptr};
 
     if (!JS_ReadStructuredClone(
             aCx, mCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
             JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
             JS::CloneDataPolicy(), &callbacks, &mCloneReadInfo)) {
       return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
@@ -2566,17 +2566,18 @@ bool IDBObjectStore::ValueWrapper::Clone
 
   static const JSStructuredCloneCallbacks callbacks = {
       CopyingStructuredCloneReadCallback /* read */,
       CopyingStructuredCloneWriteCallback /* write */,
       nullptr /* reportError */,
       nullptr /* readTransfer */,
       nullptr /* writeTransfer */,
       nullptr /* freeTransfer */,
-      nullptr /* canTransfer */
+      nullptr /* canTransfer */,
+      nullptr /* sabCloned */
   };
 
   StructuredCloneInfo cloneInfo;
 
   JS::Rooted<JS::Value> clonedValue(aCx);
   if (!JS_StructuredClone(aCx, mValue, &clonedValue, &callbacks, &cloneInfo)) {
     return false;
   }
--- a/js/public/StructuredClone.h
+++ b/js/public/StructuredClone.h
@@ -324,24 +324,38 @@ typedef void (*FreeTransferStructuredClo
  * Called when the transferring objects are checked. If this function returns
  * false, the serialization ends throwing a DataCloneError exception.
  */
 typedef bool (*CanTransferStructuredCloneOp)(JSContext* cx,
                                              JS::Handle<JSObject*> obj,
                                              bool* sameProcessScopeRequired,
                                              void* closure);
 
+/**
+ * Called when a SharedArrayBuffer (including one owned by a Wasm memory object)
+ * has been processed in context `cx` by structured cloning.  If `receiving` is
+ * true then the SAB has been received from a channel and a new SAB object has
+ * been created; if false then an existing SAB has been serialized onto a
+ * channel.
+ *
+ * If the callback returns false then the clone operation (read or write) will
+ * signal a failure.
+ */
+typedef bool (*SharedArrayBufferClonedOp)(JSContext* cx, bool receiving,
+                                          void* closure);
+
 struct JSStructuredCloneCallbacks {
   ReadStructuredCloneOp read;
   WriteStructuredCloneOp write;
   StructuredCloneErrorOp reportError;
   ReadTransferStructuredCloneOp readTransfer;
   TransferStructuredCloneOp writeTransfer;
   FreeTransferStructuredCloneOp freeTransfer;
   CanTransferStructuredCloneOp canTransfer;
+  SharedArrayBufferClonedOp sabCloned;
 };
 
 enum OwnTransferablePolicy {
   /**
    * The buffer owns any Transferables that it might contain, and should
    * properly release them upon destruction.
    */
   OwnsTransferablesIfAny,
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -465,16 +465,18 @@ struct JSStructuredCloneReader {
 struct JSStructuredCloneWriter {
  public:
   explicit JSStructuredCloneWriter(JSContext* cx,
                                    JS::StructuredCloneScope scope,
                                    const JS::CloneDataPolicy& cloneDataPolicy,
                                    const JSStructuredCloneCallbacks* cb,
                                    void* cbClosure, const Value& tVal)
       : out(cx, scope),
+        callbacks(cb),
+        closure(cbClosure),
         objs(out.context()),
         counts(out.context()),
         objectEntries(out.context()),
         otherEntries(out.context()),
         memory(out.context()),
         transferable(out.context(), tVal),
         transferableObjects(out.context(), TransferableObjectsSet(cx)),
         cloneDataPolicy(cloneDataPolicy) {
@@ -522,16 +524,23 @@ struct JSStructuredCloneWriter {
 
   bool parseTransferable();
   bool transferOwnership();
 
   inline void checkStack();
 
   SCOutput out;
 
+  // The user defined callbacks that will be used to signal cloning, in some
+  // cases.
+  const JSStructuredCloneCallbacks* callbacks;
+
+  // Any value passed to the callbacks.
+  void* closure;
+
   // Vector of objects with properties remaining to be written.
   //
   // NB: These can span multiple compartments, so the compartment must be
   // entered before any manipulation is performed.
   RootedValueVector objs;
 
   // counts[i] is the number of entries of objs[i] remaining to be written.
   // counts.length() == objs.length() and sum(counts) == entries.length().
@@ -1290,20 +1299,29 @@ bool JSStructuredCloneWriter::writeShare
   }
 
   // We must serialize the length so that the buffer object arrives in the
   // receiver with the same length, and not with the length read from the
   // rawbuf - that length can be different, and it can change at any time.
 
   intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
   uint32_t byteLength = sharedArrayBuffer->byteLength();
-  return out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
-                       static_cast<uint32_t>(sizeof(p))) &&
-         out.writeBytes(&byteLength, sizeof(byteLength)) &&
-         out.writeBytes(&p, sizeof(p));
+  if (!out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
+                     static_cast<uint32_t>(sizeof(p))) &&
+      out.writeBytes(&byteLength, sizeof(byteLength)) &&
+      out.writeBytes(&p, sizeof(p))) {
+    return false;
+  }
+
+  if (callbacks && callbacks->sabCloned &&
+      !callbacks->sabCloned(context(), /*receiving=*/false, closure)) {
+    return false;
+  }
+
+  return true;
 }
 
 bool JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj) {
   MOZ_ASSERT(obj->canUnwrapAs<WasmMemoryObject>());
 
   // Check the policy here so that we can report a sane error.
   if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
     auto errorMsg =
@@ -2280,16 +2298,22 @@ bool JSStructuredCloneReader::readShared
   }
 
   JSObject* obj = SharedArrayBufferObject::New(context(), rawbuf, byteLength);
   if (!obj) {
     rawbuf->dropReference();
     return false;
   }
 
+  if (callbacks && callbacks->sabCloned &&
+      !callbacks->sabCloned(context(), /*receiving=*/true, closure)) {
+    rawbuf->dropReference();
+    return false;
+  }
+
   vp.setObject(*obj);
   return true;
 }
 
 bool JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes,
                                                    MutableHandleValue vp) {
   JSContext* cx = context();
   if (nbytes != 0) {