Bug 1659025 - Implement [Transferable] for ReadableStream r=smaug,sfink

Differential Revision: https://phabricator.services.mozilla.com/D139525
This commit is contained in:
Kagami Sascha Rosylight 2022-05-19 11:54:31 +00:00
parent ff14b28782
commit 2b7f799d55
32 changed files with 1550 additions and 214 deletions

View file

@ -46,12 +46,18 @@
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/OffscreenCanvas.h"
#include "mozilla/dom/OffscreenCanvasBinding.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/ReadableStreamBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/StructuredCloneBlob.h"
#include "mozilla/dom/StructuredCloneHolderBinding.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TransformStream.h"
#include "mozilla/dom/TransformStreamBinding.h"
#include "mozilla/dom/WebIDLSerializable.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/dom/WritableStreamBinding.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/fallible.h"
@ -1115,7 +1121,26 @@ bool StructuredCloneHolder::CustomWriteHandler(
return WriteFullySerializableObjects(aCx, aWriter, aObj);
}
bool StructuredCloneHolder::CustomReadTransferHandler(
already_AddRefed<MessagePort> StructuredCloneHolder::ReceiveMessagePort(
uint64_t aIndex) {
if (NS_WARN_IF(aIndex >= mPortIdentifiers.Length())) {
return nullptr;
}
UniqueMessagePortId portId(mPortIdentifiers[aIndex]);
ErrorResult rv;
RefPtr<MessagePort> port = MessagePort::Create(mGlobal, portId, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return nullptr;
}
return port.forget();
}
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
MOZ_CAN_RUN_SCRIPT_BOUNDARY bool
StructuredCloneHolder::CustomReadTransferHandler(
JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
void* aContent, uint64_t aExtraData,
JS::MutableHandleObject aReturnObject) {
@ -1127,16 +1152,10 @@ bool StructuredCloneHolder::CustomReadTransferHandler(
return false;
}
#endif
MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
UniqueMessagePortId portIdentifier(mPortIdentifiers[aExtraData]);
ErrorResult rv;
RefPtr<MessagePort> port = MessagePort::Create(mGlobal, portIdentifier, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
RefPtr<MessagePort> port = ReceiveMessagePort(aExtraData);
if (!port) {
return false;
}
mTransferredPorts.AppendElement(port);
JS::Rooted<JS::Value> value(aCx);
@ -1186,10 +1205,56 @@ bool StructuredCloneHolder::CustomReadTransferHandler(
return true;
}
if (aTag == SCTAG_DOM_READABLESTREAM) {
#ifdef FUZZING
if (aExtraData >= mPortIdentifiers.Length()) {
return false;
}
#endif
RefPtr<MessagePort> port = ReceiveMessagePort(aExtraData);
if (!port) {
return false;
}
nsCOMPtr<nsIGlobalObject> global = mGlobal;
return ReadableStream::ReceiveTransfer(aCx, global, *port, aReturnObject);
}
if (aTag == SCTAG_DOM_WRITABLESTREAM) {
#ifdef FUZZING
if (aExtraData >= mPortIdentifiers.Length()) {
return false;
}
#endif
RefPtr<MessagePort> port = ReceiveMessagePort(aExtraData);
if (!port) {
return false;
}
nsCOMPtr<nsIGlobalObject> global = mGlobal;
return WritableStream::ReceiveTransfer(aCx, global, *port, aReturnObject);
}
if (aTag == SCTAG_DOM_TRANSFORMSTREAM) {
#ifdef FUZZING
if (aExtraData + 1 >= mPortIdentifiers.Length()) {
return false;
}
#endif
RefPtr<MessagePort> port1 = ReceiveMessagePort(aExtraData);
RefPtr<MessagePort> port2 = ReceiveMessagePort(aExtraData + 1);
if (!port1 || !port2) {
return false;
}
nsCOMPtr<nsIGlobalObject> global = mGlobal;
return TransformStream::ReceiveTransfer(aCx, global, *port1, *port2,
aReturnObject);
}
return false;
}
bool StructuredCloneHolder::CustomWriteTransferHandler(
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
MOZ_CAN_RUN_SCRIPT_BOUNDARY bool
StructuredCloneHolder::CustomWriteTransferHandler(
JSContext* aCx, JS::Handle<JSObject*> aObj, uint32_t* aTag,
JS::TransferableOwnership* aOwnership, void** aContent,
uint64_t* aExtraData) {
@ -1263,6 +1328,68 @@ bool StructuredCloneHolder::CustomWriteTransferHandler(
return true;
}
}
{
RefPtr<ReadableStream> stream;
rv = UNWRAP_OBJECT(ReadableStream, &obj, stream);
if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(stream);
*aTag = SCTAG_DOM_READABLESTREAM;
*aOwnership = JS::SCTAG_TMO_CUSTOM;
*aContent = nullptr;
UniqueMessagePortId id;
if (!stream->Transfer(aCx, id)) {
return false;
}
*aExtraData = mPortIdentifiers.Length();
mPortIdentifiers.AppendElement(id.release());
return true;
}
}
{
RefPtr<WritableStream> stream;
rv = UNWRAP_OBJECT(WritableStream, &obj, stream);
if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(stream);
*aTag = SCTAG_DOM_WRITABLESTREAM;
*aOwnership = JS::SCTAG_TMO_CUSTOM;
*aContent = nullptr;
UniqueMessagePortId id;
if (!stream->Transfer(aCx, id)) {
return false;
}
*aExtraData = mPortIdentifiers.Length();
mPortIdentifiers.AppendElement(id.release());
return true;
}
}
{
RefPtr<TransformStream> stream;
rv = UNWRAP_OBJECT(TransformStream, &obj, stream);
if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(stream);
*aTag = SCTAG_DOM_TRANSFORMSTREAM;
*aOwnership = JS::SCTAG_TMO_CUSTOM;
*aContent = nullptr;
UniqueMessagePortId id1;
UniqueMessagePortId id2;
if (!stream->Transfer(aCx, id1, id2)) {
return false;
}
*aExtraData = mPortIdentifiers.Length();
mPortIdentifiers.AppendElement(id1.release());
mPortIdentifiers.AppendElement(id2.release());
return true;
}
}
}
return false;
@ -1301,6 +1428,31 @@ void StructuredCloneHolder::CustomFreeTransferHandler(
delete data;
return;
}
if (aTag == SCTAG_DOM_READABLESTREAM || aTag == SCTAG_DOM_WRITABLESTREAM) {
MOZ_ASSERT(!aContent);
#ifdef FUZZING
if (aExtraData >= mPortIdentifiers.Length()) {
return;
}
#endif
MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
return;
}
if (aTag == SCTAG_DOM_TRANSFORMSTREAM) {
MOZ_ASSERT(!aContent);
#ifdef FUZZING
if (aExtraData + 1 >= mPortIdentifiers.Length()) {
return;
}
#endif
MOZ_ASSERT(aExtraData + 1 < mPortIdentifiers.Length());
MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
MessagePort::ForceClose(mPortIdentifiers[aExtraData + 1]);
return;
}
}
bool StructuredCloneHolder::CustomCanTransferHandler(
@ -1342,6 +1494,40 @@ bool StructuredCloneHolder::CustomCanTransferHandler(
}
}
{
ReadableStream* stream = nullptr;
nsresult rv = UNWRAP_OBJECT(ReadableStream, &obj, stream);
if (NS_SUCCEEDED(rv)) {
// https://streams.spec.whatwg.org/#ref-for-transfer-steps
// Step 1: If ! IsReadableStreamLocked(value) is true, throw a
// "DataCloneError" DOMException.
return !IsReadableStreamLocked(stream);
}
}
{
WritableStream* stream = nullptr;
nsresult rv = UNWRAP_OBJECT(WritableStream, &obj, stream);
if (NS_SUCCEEDED(rv)) {
// https://streams.spec.whatwg.org/#ref-for-transfer-steps①
// Step 1: If ! IsWritableStreamLocked(value) is true, throw a
// "DataCloneError" DOMException.
return !IsWritableStreamLocked(stream);
}
}
{
TransformStream* stream = nullptr;
nsresult rv = UNWRAP_OBJECT(TransformStream, &obj, stream);
if (NS_SUCCEEDED(rv)) {
// https://streams.spec.whatwg.org/#ref-for-transfer-steps②
// Step 3 + 4: If ! Is{Readable,Writable}StreamLocked(value) is true,
// throw a "DataCloneError" DOMException.
return !IsReadableStreamLocked(stream->Readable()) &&
!IsWritableStreamLocked(stream->Writable());
}
}
return false;
}

View file

@ -335,6 +335,8 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
void SameProcessScopeRequired(bool* aSameProcessScopeRequired);
already_AddRefed<MessagePort> ReceiveMessagePort(uint64_t aIndex);
bool mSupportsCloning;
bool mSupportsTransferring;

View file

@ -140,6 +140,12 @@ enum StructuredCloneTags : uint32_t {
SCTAG_DOM_CLONED_ERROR_OBJECT,
SCTAG_DOM_READABLESTREAM,
SCTAG_DOM_WRITABLESTREAM,
SCTAG_DOM_TRANSFORMSTREAM,
// IMPORTANT: If you plan to add an new IDB tag, it _must_ be add before the
// "less stable" tags!
};

View file

@ -51,6 +51,7 @@ class EventMessageAutoOverride;
class ExtendableEvent;
class KeyboardEvent;
class MouseEvent;
class MessageEvent;
class TimeEvent;
class UIEvent;
class WantsPopupControlCheck;
@ -127,6 +128,9 @@ class Event : public nsISupports, public nsWrapperCache {
// CustomEvent has a non-autogeneratable initCustomEvent.
virtual CustomEvent* AsCustomEvent() { return nullptr; }
// MessageEvent has a non-autogeneratable initMessageEvent and more.
virtual MessageEvent* AsMessageEvent() { return nullptr; }
void InitEvent(const nsAString& aEventTypeArg, bool aCanBubble,
bool aCancelable) {
InitEvent(aEventTypeArg, aCanBubble ? CanBubble::eYes : CanBubble::eNo,

View file

@ -43,8 +43,10 @@ class MessageEvent final : public Event {
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MessageEvent, Event)
virtual JSObject* WrapObjectInternal(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
JSObject* WrapObjectInternal(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
MessageEvent* AsMessageEvent() override { return this; }
void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aData,
ErrorResult& aRv);

View file

@ -19,7 +19,6 @@
#include "mozilla/dom/ByteStreamHelpers.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/ReadableByteStreamController.h"
#include "mozilla/dom/ReadableByteStreamControllerBinding.h"
#include "mozilla/dom/ReadIntoRequest.h"

View file

@ -37,6 +37,8 @@ using OwningReadableStreamReader =
OwningReadableStreamDefaultReaderOrReadableStreamBYOBReader;
class NativeUnderlyingSource;
class BodyStreamHolder;
class UniqueMessagePortId;
class MessagePort;
class ReadableStream final : public nsISupports, public nsWrapperCache {
public:
@ -95,6 +97,15 @@ class ReadableStream final : public nsISupports, public nsWrapperCache {
void ReleaseObjects();
// [Transferable]
// https://html.spec.whatwg.org/multipage/structured-data.html#transfer-steps
MOZ_CAN_RUN_SCRIPT bool Transfer(JSContext* aCx,
UniqueMessagePortId& aPortId);
// https://html.spec.whatwg.org/multipage/structured-data.html#transfer-receiving-steps
static MOZ_CAN_RUN_SCRIPT bool ReceiveTransfer(
JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
JS::MutableHandle<JSObject*> aReturnObject);
public:
nsIGlobalObject* GetParentObject() const { return mGlobal; }

View file

@ -12,7 +12,6 @@
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/ReadableStreamController.h"
#include "mozilla/dom/ReadableStreamDefaultController.h"
@ -503,7 +502,8 @@ void SetUpReadableStreamDefaultController(
}
// Step 10.
RefPtr<Promise> startPromise = Promise::Create(GetIncumbentGlobal(), aRv);
RefPtr<Promise> startPromise =
Promise::Create(aStream->GetParentObject(), aRv);
if (aRv.Failed()) {
return;
}

View file

@ -8,7 +8,6 @@
#include "js/TypeDecls.h"
#include "js/experimental/TypedData.h"
#include "mozilla/dom/ByteStreamHelpers.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/ReadIntoRequest.h"
#include "mozilla/dom/ReadableStream.h"

1065
dom/streams/Transferable.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,6 @@
#include "mozilla/Attributes.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/RootedDictionary.h"
@ -45,6 +44,13 @@ TransformStream::TransformStream(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {
mozilla::HoldJSObjects(this);
}
TransformStream::TransformStream(nsIGlobalObject* aGlobal,
ReadableStream* aReadable,
WritableStream* aWritable)
: mGlobal(aGlobal), mReadable(aReadable), mWritable(aWritable) {
mozilla::HoldJSObjects(this);
}
TransformStream::~TransformStream() { mozilla::DropJSObjects(this); }
JSObject* TransformStream::WrapObject(JSContext* aCx,

View file

@ -19,12 +19,17 @@ namespace mozilla::dom {
class WritableStream;
class ReadableStream;
class UniqueMessagePortId;
class MessagePort;
class TransformStream final : public nsISupports, public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TransformStream)
TransformStream(nsIGlobalObject* aGlobal, ReadableStream* aReadable,
WritableStream* aWritable);
// Internal slot accessors
bool Backpressure() const { return mBackpressure; }
void SetBackpressure(bool aBackpressure) { mBackpressure = aBackpressure; }
@ -42,6 +47,16 @@ class TransformStream final : public nsISupports, public nsWrapperCache {
MOZ_KNOWN_LIVE ReadableStream* Readable() { return mReadable; }
MOZ_KNOWN_LIVE WritableStream* Writable() { return mWritable; }
// [Transferable]
// https://html.spec.whatwg.org/multipage/structured-data.html#transfer-steps
MOZ_CAN_RUN_SCRIPT bool Transfer(JSContext* aCx,
UniqueMessagePortId& aPortId1,
UniqueMessagePortId& aPortId2);
// https://html.spec.whatwg.org/multipage/structured-data.html#transfer-receiving-steps
static MOZ_CAN_RUN_SCRIPT bool ReceiveTransfer(
JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort1,
MessagePort& aPort2, JS::MutableHandle<JSObject*> aReturnObject);
protected:
~TransformStream();
explicit TransformStream(nsIGlobalObject* aGlobal);

View file

@ -25,6 +25,8 @@ class Promise;
class WritableStreamDefaultController;
class WritableStreamDefaultWriter;
class UnderlyingSinkAlgorithmsBase;
class UniqueMessagePortId;
class MessagePort;
class WritableStream : public nsISupports, public nsWrapperCache {
public:
@ -41,7 +43,6 @@ class WritableStream : public nsISupports, public nsWrapperCache {
enum class WriterState { Writable, Closed, Erroring, Errored };
// Slot Getter/Setters:
public:
bool Backpressure() const { return mBackpressure; }
void SetBackpressure(bool aBackpressure) { mBackpressure = aBackpressure; }
@ -133,7 +134,15 @@ class WritableStream : public nsISupports, public nsWrapperCache {
// WritableStreamUpdateBackpressure
void UpdateBackpressure(bool aBackpressure, ErrorResult& aRv);
public:
// [Transferable]
// https://html.spec.whatwg.org/multipage/structured-data.html#transfer-steps
MOZ_CAN_RUN_SCRIPT bool Transfer(JSContext* aCx,
UniqueMessagePortId& aPortId);
// https://html.spec.whatwg.org/multipage/structured-data.html#transfer-receiving-steps
static MOZ_CAN_RUN_SCRIPT bool ReceiveTransfer(
JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
JS::MutableHandle<JSObject*> aReturnObject);
nsIGlobalObject* GetParentObject() const { return mGlobal; }
JSObject* WrapObject(JSContext* aCx,

View file

@ -12,11 +12,9 @@
#include "mozilla/dom/AbortSignal.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/dom/WritableStreamDefaultController.h"
#include "mozilla/dom/WritableStreamDefaultControllerBinding.h"
// #include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
#include "mozilla/dom/UnderlyingSinkBinding.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDebug.h"
@ -185,7 +183,8 @@ void SetUpWritableStreamDefaultController(
}
// Step 16. Let startPromise be a promise resolved with startResult.
RefPtr<Promise> startPromise = Promise::Create(GetIncumbentGlobal(), aRv);
RefPtr<Promise> startPromise =
Promise::Create(aStream->GetParentObject(), aRv);
if (aRv.Failed()) {
return;
}

View file

@ -50,6 +50,7 @@ UNIFIED_SOURCES += [
"ReadableStreamTee.cpp",
"StreamUtils.cpp",
"TeeState.cpp",
"Transferable.cpp",
"TransformerCallbackHelpers.cpp",
"TransformStream.cpp",
"TransformStreamDefaultController.cpp",
@ -60,10 +61,13 @@ UNIFIED_SOURCES += [
"WritableStreamDefaultWriter.cpp",
]
include("/ipc/chromium/chromium-config.mozbuild") # to import MessagePort.h
FINAL_LIBRARY = "xul"
LOCAL_INCLUDES += [
"/dom/base",
"/dom/ipc",
]
# MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]

View file

@ -1,5 +1,5 @@
[Exposed=*,
//Transferable See Bug 1734240
//Transferable See Bug 1562065
]
interface ReadableStream {
[Throws]

View file

@ -18,6 +18,7 @@ namespace net {
class ChannelEvent;
class ChannelEventQueue;
class MessageEvent;
class WebSocketChannelChild final : public BaseWebSocketChannel,
public PWebSocketChild,

View file

@ -1,18 +0,0 @@
[messagechannel.any.sharedworker.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED
[messagechannel.any.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED
[messagechannel.any.worker.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED
[messagechannel.any.serviceworker.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED

View file

@ -1,3 +0,0 @@
[window-postmessage.window.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED

View file

@ -1,8 +0,0 @@
[structured-clone.any.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED
[structured-clone.any.worker.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED

View file

@ -1,8 +0,0 @@
[deserialize-error.window.html]
expected: ERROR
[a ReadableStream deserialization failure should result in a DataCloneError]
expected: TIMEOUT
[a WritableStream deserialization failure should result in a DataCloneError]
expected: TIMEOUT

View file

@ -1,49 +1,3 @@
[readable-stream.html]
[sending ten chunks on demand should work]
expected: FAIL
[race between cancel() and enqueue() should be benign]
expected: FAIL
[transferring a non-serializable chunk should error both sides]
expected: FAIL
[stream cancel should not wait for underlying source cancel]
expected: FAIL
[cancel should abort a pending read()]
expected: FAIL
[race between cancel() and error() should leave sides in different states]
expected: FAIL
[cancel should be propagated to the original]
expected: FAIL
[race between cancel() and close() should be benign]
expected: FAIL
[errors should be passed through]
expected: FAIL
[the extra queue from transferring is counted in chunks]
expected: FAIL
[sending one chunk through a transferred stream should work]
expected: FAIL
[transferring a stream should relieve backpressure]
expected: FAIL
[sending ten chunks through a transferred stream should work]
expected: FAIL
[serialization should not happen until the value is read]
expected: FAIL
[sending ten chunks one at a time should work]
expected: FAIL
[transferring a stream should add one chunk to the queue size]
expected: FAIL

View file

@ -2,45 +2,18 @@
[a TypeError message should not be preserved if it is inherited]
expected: FAIL
[DOMException errors should be preserved]
expected: FAIL
[reason with a simple value of '3' should be preserved]
expected: FAIL
[URIError should be preserved]
expected: FAIL
[reason with a simple value of 'null' should be preserved]
expected: FAIL
[reason with a simple value of '7' should be preserved]
expected: FAIL
[a TypeError message should be converted to a string]
expected: FAIL
[objects that can be completely expressed in JSON should be preserved]
expected: FAIL
[reason with a simple value of 'true' should be preserved]
expected: FAIL
[TypeError should be preserved]
expected: FAIL
[reason with a simple value of 'undefined' should be preserved]
expected: FAIL
[RangeError should be preserved]
expected: FAIL
[reason with a simple value of 'NaN' should be preserved]
expected: FAIL
[objects that cannot be expressed in JSON should also be preserved]
expected: FAIL
[other attributes of a TypeError should not be preserved]
expected: FAIL
@ -50,24 +23,11 @@
[SyntaxError should be preserved]
expected: FAIL
[reason with a simple value of 'Infinity' should be preserved]
expected: FAIL
[EvalError should be preserved]
expected: FAIL
[reason with a simple value of '\t\r\n' should be preserved]
expected: FAIL
[the type and message of a TypeError should be preserved]
expected: FAIL
[reason with a simple value of 'hi' should be preserved]
expected: FAIL
[reason with a simple value of 'false' should be preserved]
expected: FAIL
[a TypeError message should not be preserved if it is a getter]
expected: FAIL

View file

@ -1,8 +0,0 @@
[service-worker.https.html]
expected:
if (os == "android") and debug and not swgl: [OK, ERROR]
[serviceWorker.controller.postMessage should be able to transfer a ReadableStream]
expected: FAIL
[postMessage in a service worker should be able to transfer ReadableStream]
expected: FAIL

View file

@ -1,7 +0,0 @@
[shared-worker.html]
[postMessage in a worker should be able to transfer a ReadableStream]
expected: FAIL
[worker.postMessage should be able to transfer a ReadableStream]
expected: FAIL

View file

@ -1,6 +0,0 @@
[transform-stream.html]
[piping through transferred transforms should work]
expected: FAIL
[window.postMessage should be able to transfer a TransformStream]
expected: FAIL

View file

@ -1,12 +0,0 @@
[window.html]
[transfer to and from an iframe should work]
expected: FAIL
[the same ReadableStream posted multiple times should arrive together]
expected: FAIL
[port.postMessage should be able to transfer a ReadableStream]
expected: FAIL
[window.postMessage should be able to transfer a ReadableStream]
expected: FAIL

View file

@ -1,11 +0,0 @@
[worker.html]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [OK, ERROR]
[terminating a worker should not error the stream]
expected: FAIL
[postMessage in a worker should be able to transfer a ReadableStream]
expected: FAIL
[worker.postMessage should be able to transfer a ReadableStream]
expected: FAIL

View file

@ -1,22 +1,6 @@
[writable-stream.html]
[window.postMessage should be able to transfer a WritableStream]
expected: FAIL
[second write should wait for first underlying write to complete]
expected: FAIL
[abort() should work]
expected: FAIL
[window.postMessage should be able to transfer a {readable, writable} pair]
expected: FAIL
[writing a unclonable object should error the stream]
expected: FAIL
[effective queue size of a transferred writable should be 2]
expected: FAIL
[desiredSize for a newly-transferred stream should be 1]
expected: FAIL

View file

@ -1,3 +0,0 @@
[dedicated.html]
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED

View file

@ -1,5 +0,0 @@
[shared.html]
disabled:
if os == "win": Bug 1661351
[A subclass instance will be received as its closest transferable superclass]
expected: PRECONDITION_FAILED

View file

@ -0,0 +1,219 @@
"use strict";
function receiveEventOnce(target, name) {
return new Promise(resolve => {
target.addEventListener(
name,
ev => {
resolve(ev);
},
{ once: true }
);
});
}
async function postAndTestMessageEvent(data, transfer, title) {
postMessage(data, "*", transfer);
const messagePortCount = transfer.filter(i => i instanceof MessagePort)
.length;
const ev = await receiveEventOnce(window, "message");
assert_equals(
ev.ports.length,
messagePortCount,
`Correct number of ports ${title}`
);
for (const [i, port] of ev.ports.entries()) {
assert_true(
port instanceof MessagePort,
`ports[${i}] include MessagePort ${title}`
);
}
for (const [key, value] of Object.entries(data)) {
assert_true(
ev.data[key] instanceof value.constructor,
`data.${key} has correct interface ${value.constructor.name} ${title}`
);
}
}
async function transferMessagePortWithOrder1(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ stream, port2: channel.port2 },
[stream, channel.port2],
`when transferring [${stream.constructor.name}, MessagePort]`
);
}
async function transferMessagePortWithOrder2(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ stream, port2: channel.port2 },
[channel.port2, stream],
`when transferring [MessagePort, ${stream.constructor.name}]`
);
}
async function transferMessagePortWithOrder3(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ port1: channel.port1, stream, port2: channel.port2 },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort]`
);
}
async function transferMessagePortWithOrder4(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{},
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with empty data`
);
}
async function transferMessagePortWithOrder5(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ port2: channel.port2, port1: channel.port1, stream },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with data having different order`
);
}
async function transferMessagePortWithOrder6(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ port2: channel.port2, port1: channel.port1 },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with stream not being in the data`
);
}
async function transferMessagePortWithOrder7(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ stream },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with ports not being in the data`
);
}
async function transferMessagePortWith(constructor) {
await transferMessagePortWithOrder1(new constructor());
await transferMessagePortWithOrder2(new constructor());
await transferMessagePortWithOrder3(new constructor());
}
async function advancedTransferMesagePortWith(constructor) {
await transferMessagePortWithOrder4(new constructor());
await transferMessagePortWithOrder5(new constructor());
await transferMessagePortWithOrder6(new constructor());
await transferMessagePortWithOrder7(new constructor());
}
async function mixedTransferMessagePortWithOrder1() {
const channel = new MessageChannel();
const readable = new ReadableStream();
const writable = new WritableStream();
const transform = new TransformStream();
await postAndTestMessageEvent(
{
readable,
writable,
transform,
port1: channel.port1,
port2: channel.port2,
},
[readable, writable, transform, channel.port1, channel.port2],
`when transferring [ReadableStream, WritableStream, TransformStream, MessagePort, MessagePort]`
);
}
async function mixedTransferMessagePortWithOrder2() {
const channel = new MessageChannel();
const readable = new ReadableStream();
const writable = new WritableStream();
const transform = new TransformStream();
await postAndTestMessageEvent(
{ readable, writable, transform },
[transform, channel.port1, readable, channel.port2, writable],
`when transferring [TransformStream, MessagePort, ReadableStream, MessagePort, WritableStream]`
);
}
async function mixedTransferMessagePortWithOrder3() {
const channel = new MessageChannel();
const readable1 = new ReadableStream();
const readable2 = new ReadableStream();
const writable1 = new WritableStream();
const writable2 = new WritableStream();
const transform1 = new TransformStream();
const transform2 = new TransformStream();
await postAndTestMessageEvent(
{ readable1, writable1, transform1, readable2, writable2, transform2 },
[
transform2,
channel.port1,
readable1,
channel.port2,
writable2,
readable2,
writable1,
transform1,
],
`when transferring [TransformStream, MessagePort, ReadableStream, MessagePort, WritableStream, ReadableStream, WritableStream, TransformStream] but with the data having different order`
);
}
async function mixedTransferMesagePortWith() {
await mixedTransferMessagePortWithOrder1();
await mixedTransferMessagePortWithOrder2();
await mixedTransferMessagePortWithOrder3();
}
promise_test(async t => {
await transferMessagePortWith(ReadableStream);
}, "Transferring a MessagePort with a ReadableStream should set `.ports`");
promise_test(async t => {
await transferMessagePortWith(WritableStream);
}, "Transferring a MessagePort with a WritableStream should set `.ports`");
promise_test(async t => {
await transferMessagePortWith(TransformStream);
}, "Transferring a MessagePort with a TransformStream should set `.ports`");
promise_test(async t => {
await transferMessagePortWith(ReadableStream);
}, "Transferring a MessagePort with a ReadableStream should set `.ports`, advanced");
promise_test(async t => {
await transferMessagePortWith(WritableStream);
}, "Transferring a MessagePort with a WritableStream should set `.ports`, advanced");
promise_test(async t => {
await transferMessagePortWith(TransformStream);
}, "Transferring a MessagePort with a TransformStream should set `.ports`, advanced");
promise_test(async t => {
await mixedTransferMesagePortWith();
}, "Transferring a MessagePort with multiple streams should set `.ports`");
test(() => {
assert_throws_dom("DataCloneError", () =>
postMessage({ stream: new ReadableStream() }, "*")
);
}, "ReadableStream must not be serializable");
test(() => {
assert_throws_dom("DataCloneError", () =>
postMessage({ stream: new WritableStream() }, "*")
);
}, "WritableStream must not be serializable");
test(() => {
assert_throws_dom("DataCloneError", () =>
postMessage({ stream: new TransformStream() }, "*")
);
}, "TransformStream must not be serializable");