Repository: nodejs/node
Pull request: stream: set stream prototype to closest transferable superclass

The latest WPT test requires this behaviour. The `ReflectConstruct` way did this well, but there was [an attempt to reduce the `ReflectConstruct` overhead](https://github.com/nodejs/node/pull/50107) breaking this, since it introduced a "closer" superclass under `Readable` (or `Writable` etc.). 

Fixes: https://github.com/nodejs/node/issues/54603
Refs: https://github.com/nodejs/node/pull/50107

Changed files:
filename: lib/internal/webstreams/readablestream.js
patch: @@ -639,6 +639,7 @@ ObjectDefineProperties(ReadableStream, {
 });
 
 function InternalTransferredReadableStream() {
+  ObjectSetPrototypeOf(this, ReadableStream.prototype);
   markTransferMode(this, false, true);
   this[kType] = 'ReadableStream';
   this[kState] = createReadableStreamState();
@@ -1226,6 +1227,7 @@ ObjectDefineProperties(ReadableByteStreamController.prototype, {
 });
 
 function InternalReadableStream(start, pull, cancel, highWaterMark, size) {
+  ObjectSetPrototypeOf(this, ReadableStream.prototype);
   markTransferMode(this, false, true);
   this[kType] = 'ReadableStream';
   this[kState] = createReadableStreamState();
@@ -1253,6 +1255,7 @@ function createReadableStream(start, pull, cancel, highWaterMark = 1, size = ()
 }
 
 function InternalReadableByteStream(start, pull, cancel) {
+  ObjectSetPrototypeOf(this, ReadableStream.prototype);
   markTransferMode(this, false, true);
   this[kType] = 'ReadableStream';
   this[kState] = createReadableStreamState();

filename: lib/internal/webstreams/transformstream.js
patch: @@ -256,6 +256,7 @@ ObjectDefineProperties(TransformStream.prototype, {
 });
 
 function InternalTransferredTransformStream() {
+  ObjectSetPrototypeOf(this, TransformStream.prototype);
   markTransferMode(this, false, true);
   this[kType] = 'TransformStream';
   this[kState] = {

filename: lib/internal/webstreams/writablestream.js
patch: @@ -300,6 +300,7 @@ ObjectDefineProperties(WritableStream.prototype, {
 });
 
 function InternalTransferredWritableStream() {
+  ObjectSetPrototypeOf(this, WritableStream.prototype);
   markTransferMode(this, false, true);
   this[kType] = 'WritableStream';
   this[kState] = createWritableStreamState();
@@ -516,6 +517,7 @@ ObjectDefineProperties(WritableStreamDefaultController.prototype, {
 });
 
 function InternalWritableStream(start, write, close, abort, highWaterMark, size) {
+  ObjectSetPrototypeOf(this, WritableStream.prototype);
   markTransferMode(this, false, true);
   this[kType] = 'WritableStream';
   this[kState] = createWritableStreamState();

filename: test/parallel/test-structuredClone-global.js
patch: @@ -16,6 +16,20 @@ assert.strictEqual(structuredClone(undefined, null), undefined);
 assert.strictEqual(structuredClone(undefined, { transfer: null }), undefined);
 assert.strictEqual(structuredClone(undefined, { }), undefined);
 
+// Transferables or its subclasses should be received with its closest transferable superclass
+for (const StreamClass of [ReadableStream, WritableStream, TransformStream]) {
+  const original = new StreamClass();
+  const transfer = structuredClone(original, { transfer: [original] });
+  assert.strictEqual(Object.getPrototypeOf(transfer), StreamClass.prototype);
+  assert.ok(transfer instanceof StreamClass);
+
+  const extended = class extends StreamClass {};
+  const extendedOriginal = new extended();
+  const extendedTransfer = structuredClone(extendedOriginal, { transfer: [extendedOriginal] });
+  assert.strictEqual(Object.getPrototypeOf(extendedTransfer), StreamClass.prototype);
+  assert.ok(extendedTransfer instanceof StreamClass);
+}
+
 {
   // See: https://github.com/nodejs/node/issues/49940
   const cloned = structuredClone({}, {