Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/internal/worker/js_transferable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const {
ArrayPrototypeForEach,
Error,
StringPrototypeSplit,
} = primordials;
Expand All @@ -9,6 +10,7 @@ const {
},
} = require('internal/errors');
const webidl = require('internal/webidl');
const { isTransformStream } = require('internal/streams/utils');
const {
messaging_deserialize_symbol,
messaging_transfer_symbol,
Expand All @@ -18,6 +20,7 @@ const {
const {
setDeserializerCreateObjectFunction,
structuredClone: nativeStructuredClone,
DOMException,
} = internalBinding('messaging');
const {
privateSymbols: {
Expand Down Expand Up @@ -123,6 +126,17 @@ function structuredClone(value, options) {
},
);

const transferList = idlOptions.transfer;
ArrayPrototypeForEach(transferList, (outer, i) => {
if (isTransformStream(outer)) {
ArrayPrototypeForEach(transferList, (inner, j) => {
if (i !== j && (inner === outer.readable || inner === outer.writable)) {
throw new DOMException('Cannot transfer a stream and its members', 'DataCloneError');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These steps aren't explicitly listed in the specification for structuredClone. It also seems weird to handle a highly specific edge case like this here, especially since there are many other ways than just structuredClone that can trigger a transfer, e.g. postMessage. If we really needed a workaround specific to TransformStreams, I would expect to find it near the implementation of its transfer steps, so that it is handled correctly regardless of how the transfer was triggered.

But to be honest, I don't think we should be fixing the test failure by adding extra code to detect this exact edge case. The streams specification requires that the transfer fails if the readable or writable side of the TransformStream is locked, which should be the case if either side was transferred before. It seems like Node's current implementation doesn't do these checks correctly, and we should investigate and fix the root cause of that problem instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking a bit closer, I believe the transfer steps for TransformStream are not implemented correctly. Steps 3 and 4 of the specification require readable and writable to be structurally cloned during the transfer steps of TransformStream. However, Node's implementation simply creates a data object with the not-yet-transferred readable and writable streams... 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MattiasBuelens I really appreciate your detailed explanation! I'll review it again based on what you said.🙂

}
});
}
});

const serializedData = nativeStructuredClone(value, idlOptions);
return serializedData;
}
Expand Down
8 changes: 0 additions & 8 deletions test/wpt/status/streams.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@
"transferable/transfer-with-messageport.window.js": {
"skip": "Browser-specific test"
},
"transferable/transform-stream-members.any.js": {
"fail": {
"expected": [
"Transferring [object TransformStream],[object ReadableStream] should fail",
"Transferring [object TransformStream],[object WritableStream] should fail"
]
}
},
"transform-streams/invalid-realm.tentative.window.js": {
"skip": "Browser-specific test"
}
Expand Down
Loading