diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp index b569c8a5ad..7b7dbdf67d 100644 --- a/dom/base/StructuredCloneHolder.cpp +++ b/dom/base/StructuredCloneHolder.cpp @@ -152,11 +152,6 @@ struct SameThreadStreamTransferData JS::PersistentRootedObject mRecord; }; -struct MessagePortStreamTransferData -{ - MessagePortIdentifier mIdentifier; -}; - bool CallStreamTransferHelper(JSContext* aCx, const char* aHelperName, @@ -235,6 +230,7 @@ TryWriteSameThreadStreamTransfer(JSContext* aCx, bool TryWriteReadableStreamPortTransfer(JSContext* aCx, JS::Handle aObj, + nsTArray& aPortIdentifiers, uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, @@ -265,13 +261,13 @@ TryWriteReadableStreamPortTransfer(JSContext* aCx, return false; } - MessagePortStreamTransferData* data = new MessagePortStreamTransferData(); - port->CloneAndDisentangle(data->mIdentifier); + *aExtraData = aPortIdentifiers.Length(); + MessagePortIdentifier* identifier = aPortIdentifiers.AppendElement(); + port->CloneAndDisentangle(*identifier); *aTag = SCTAG_DOM_TRANSFERRED_READABLESTREAM; *aOwnership = JS::SCTAG_TMO_CUSTOM; - *aContent = data; - *aExtraData = 0; + *aContent = nullptr; *aHandled = true; return true; } @@ -304,18 +300,19 @@ ReadSameThreadStreamTransfer(JSContext* aCx, bool ReadReadableStreamPortTransfer(JSContext* aCx, nsISupports* aParent, - void* aContent, + nsTArray& aPortIdentifiers, + uint64_t aExtraData, JS::MutableHandleObject aReturnObject) { - MOZ_ASSERT(aContent); - MessagePortStreamTransferData* data = - static_cast(aContent); + if (aExtraData >= aPortIdentifiers.Length()) { + return false; + } nsCOMPtr global = do_QueryInterface(aParent); ErrorResult rv; RefPtr port = - MessagePort::Create(global, data->mIdentifier, rv); + MessagePort::Create(global, aPortIdentifiers[aExtraData], rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); return false; @@ -339,7 +336,6 @@ ReadReadableStreamPortTransfer(JSContext* aCx, } aReturnObject.set(&result.toObject()); - delete data; return true; } @@ -1592,11 +1588,20 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx, "__uxpReceiveReadableStreamTransfer", aReturnObject); } + + if (aTag == SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM) { + return ReadSameThreadStreamTransfer(aCx, aContent, + "__uxpReceiveTransformStreamTransfer", + aReturnObject); + } } - if (mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread && + if ((mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread || + mStructuredCloneScope == StructuredCloneScope::DifferentProcess) && aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM) { - return ReadReadableStreamPortTransfer(aCx, mParent, aContent, + MOZ_ASSERT(!aContent); + return ReadReadableStreamPortTransfer(aCx, mParent, mPortIdentifiers, + aExtraData, aReturnObject); } @@ -1691,12 +1696,25 @@ StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx, if (handled) { return true; } + + if (!TryWriteSameThreadStreamTransfer(aCx, obj, + "__uxpTransferTransformStream", + SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM, + aTag, aOwnership, aContent, + aExtraData, &handled)) { + return false; + } + if (handled) { + return true; + } } - if (mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) { + if (mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread || + mStructuredCloneScope == StructuredCloneScope::DifferentProcess) { bool handled = false; - if (!TryWriteReadableStreamPortTransfer(aCx, obj, aTag, aOwnership, - aContent, aExtraData, &handled)) { + if (!TryWriteReadableStreamPortTransfer(aCx, obj, mPortIdentifiers, + aTag, aOwnership, aContent, + aExtraData, &handled)) { return false; } if (handled) { @@ -1743,7 +1761,8 @@ StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag, } if ((aTag == SCTAG_DOM_TRANSFERRED_WRITABLESTREAM || - aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM) && + aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM || + aTag == SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM) && mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread) { MOZ_ASSERT(aContent); SameThreadStreamTransferData* data = @@ -1753,12 +1772,11 @@ StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag, } if (aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM && - mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) { - MOZ_ASSERT(aContent); - MessagePortStreamTransferData* data = - static_cast(aContent); - MessagePort::ForceClose(data->mIdentifier); - delete data; + (mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread || + mStructuredCloneScope == StructuredCloneScope::DifferentProcess)) { + MOZ_ASSERT(!aContent); + MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); + MessagePort::ForceClose(mPortIdentifiers[aExtraData]); return; } } diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index 86ea88ba26..15518104f9 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -71,6 +71,7 @@ enum StructuredCloneTags { // Same-thread transferable stream records. These are not supported by IDB. SCTAG_DOM_TRANSFERRED_READABLESTREAM, SCTAG_DOM_TRANSFERRED_WRITABLESTREAM, + SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM, // When adding a new tag for IDB, please don't add it to the end of the list! // Tags that are supported by IDB must not ever change. See the static assert diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp index ba5111c57e..6103cb79c1 100644 --- a/js/src/builtin/Stream.cpp +++ b/js/src/builtin/Stream.cpp @@ -6735,6 +6735,163 @@ js::InitStreamExtras(JSContext* cx, HandleObject global) return makeDOMException("The object could not be cloned.", "DataCloneError"); } + function cloneTransferReasonError(value) { + var constructors = [ + global.EvalError, + global.RangeError, + global.ReferenceError, + global.SyntaxError, + global.TypeError, + global.URIError, + global.Error + ]; + + for (var i = 0; i < constructors.length; ++i) { + var C = constructors[i]; + if (typeof C !== "function") + continue; + try { + if (!(value instanceof C)) + continue; + } catch (e) { + continue; + } + + var message = ""; + var descriptor; + try { + descriptor = Object.getOwnPropertyDescriptor(value, "message"); + } catch (e) { + descriptor = undefined; + } + if (descriptor && hasOwn.call(descriptor, "value")) + message = String(descriptor.value); + return new C(message); + } + + return undefined; + } + + function cloneTransferReasonValue(value, memory) { + if (typeof value === "function" || typeof value === "symbol") + throw makeDataCloneError(); + if (!isObject(value)) + return value; + if (isUncloneableStreamChunk(value)) + throw makeDataCloneError(); + + memory = memory || new WeakMap(); + if (memory.has(value)) + return memory.get(value); + + if (typeof global.DOMException === "function") { + try { + if (value instanceof global.DOMException) { + return new global.DOMException(String(value.message), + String(value.name)); + } + } catch (e) {} + } + + var errorClone = cloneTransferReasonError(value); + if (errorClone !== undefined) + return errorClone; + + if (typeof global.ArrayBuffer === "function") { + try { + if (value instanceof global.ArrayBuffer) { + var bufferClone = value.slice(0); + memory.set(value, bufferClone); + return bufferClone; + } + } catch (e) {} + } + + if (global.ArrayBuffer && global.ArrayBuffer.isView && + global.ArrayBuffer.isView(value)) { + var clonedBuffer = cloneTransferReasonValue(value.buffer, memory); + var viewClone; + if (objectToString.call(value) === "[object DataView]") { + viewClone = new global.DataView(clonedBuffer, value.byteOffset, + value.byteLength); + } else { + viewClone = new value.constructor(clonedBuffer, value.byteOffset, + value.length); + } + memory.set(value, viewClone); + return viewClone; + } + + if (typeof global.Date === "function") { + try { + if (value instanceof global.Date) { + var dateClone = new global.Date(value.getTime()); + memory.set(value, dateClone); + return dateClone; + } + } catch (e) {} + } + + if (typeof global.RegExp === "function") { + try { + if (value instanceof global.RegExp) { + var flags = ""; + flags += value.global ? "g" : ""; + flags += value.ignoreCase ? "i" : ""; + flags += value.multiline ? "m" : ""; + flags += value.unicode ? "u" : ""; + flags += value.sticky ? "y" : ""; + var regexpClone = new global.RegExp(value.source, flags); + regexpClone.lastIndex = value.lastIndex; + memory.set(value, regexpClone); + return regexpClone; + } + } catch (e) {} + } + + if (typeof global.Map === "function") { + try { + if (value instanceof global.Map) { + var mapClone = new global.Map(); + memory.set(value, mapClone); + value.forEach(function(v, k) { + mapClone.set(cloneTransferReasonValue(k, memory), + cloneTransferReasonValue(v, memory)); + }); + return mapClone; + } + } catch (e) { + throw e; + } + } + + if (typeof global.Set === "function") { + try { + if (value instanceof global.Set) { + var setClone = new global.Set(); + memory.set(value, setClone); + value.forEach(function(v) { + setClone.add(cloneTransferReasonValue(v, memory)); + }); + return setClone; + } + } catch (e) { + throw e; + } + } + + var clone = Array.isArray(value) ? [] : {}; + memory.set(value, clone); + + var keys = Object.keys(value); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + clone[key] = cloneTransferReasonValue(value[key], memory); + } + + return clone; + } + function abortTransferredWritable(record, reason) { try { return record.writer.abort(reason); @@ -6908,12 +7065,16 @@ js::InitStreamExtras(JSContext* cx, HandleObject global) function cancelReadableTransfer(record, reason) { record.canceled = true; - Promise.resolve().then(function() { + var cancelPromise = Promise.resolve().then(function() { + var clonedReason = cloneTransferReasonValue(reason); try { - silenceRejection(record.reader.cancel(reason)); - } catch (e) {} + return record.reader.cancel(clonedReason); + } catch (e) { + return Promise.reject(e); + } }); - return undefined; + silenceRejection(cancelPromise); + return cancelPromise; } function closeReadableTransferPort(port) { @@ -7139,13 +7300,50 @@ js::InitStreamExtras(JSContext* cx, HandleObject global) }, cancel: function(reason) { state.canceled = true; - tryPostReadableTransferPort(port, { type: "cancel", reason: reason }); + try { + port.postMessage({ type: "cancel", reason: reason }); + } catch (e) { + closeReadableTransferPort(port); + return Promise.reject(e); + } closeReadableTransferPort(port); return undefined; } }); } + function __uxpTransferTransformStream(stream) { + if (!isObject(stream) || !transformState.has(stream)) + return undefined; + + var state = transformState.get(stream); + if (state.readable.locked || state.writable.locked) + return null; + + var readableRecord = __uxpTransferReadableStream(state.readable); + if (!readableRecord) + return null; + + var writableRecord = __uxpTransferWritableStream(state.writable); + if (!writableRecord) + return null; + + return { + readable: readableRecord, + writable: writableRecord + }; + } + + function __uxpReceiveTransformStreamTransfer(record) { + var stream = Object.create(global.TransformStream.prototype); + transformState.set(stream, { + readable: __uxpReceiveReadableStreamTransfer(record.readable), + writable: __uxpReceiveWritableStreamTransfer(record.writable), + controller: undefined + }); + return stream; + } + function __uxpReceiveReadableStreamTransfer(record) { return new ReadableStream({ start: function(controller) { @@ -7323,6 +7521,12 @@ js::InitStreamExtras(JSContext* cx, HandleObject global) Object.defineProperty(global, "__uxpReceiveReadableStreamTransferFromPort", { value: __uxpReceiveReadableStreamTransferFromPort, configurable: true }); + Object.defineProperty(global, "__uxpTransferTransformStream", + { value: __uxpTransferTransformStream, + configurable: true }); + Object.defineProperty(global, "__uxpReceiveTransformStreamTransfer", + { value: __uxpReceiveTransformStreamTransfer, + configurable: true }); if (global.ReadableStream && !global.ReadableStream.from) { Object.defineProperty(global.ReadableStream, "from", {