Skip to content

Commit 2c76a1e

Browse files
committed
stream: unify internal webstream construction
Streams created internally (transform stream sides, tee branches, ReadableStream.from, transferred streams) were built by wrapper constructors that swapped the prototype of every instance and then assigned an own, enumerable `constructor` property to look like a public stream. Each internal stream therefore had its own hidden class and `Object.keys(stream)` reported `['constructor']`. The public constructors now accept the internal construction sentinel and leave controller setup to the caller, so every ReadableStream and WritableStream shares one hidden class and no per-instance prototype swap or own property is needed. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 5c5bd22 commit 2c76a1e

3 files changed

Lines changed: 81 additions & 85 deletions

File tree

‎lib/internal/webstreams/readablestream.js‎

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,13 @@ class ReadableStream {
253253
*/
254254
constructor(source = kEmptyObject, strategy = kEmptyObject) {
255255
markTransferMode(this, false, true);
256+
// Internal construction (tee, transform streams, adapters, transfer):
257+
// the caller sets up the controller, so every ReadableStream shares
258+
// one hidden class and no per-instance prototype swap is needed.
259+
if (source === kSkipThrow) {
260+
this[kState] = createReadableStreamState();
261+
return;
262+
}
256263
validateObject(source, 'source', kValidateObjectAllowObjects);
257264
validateObject(strategy, 'strategy', kValidateObjectAllowObjectsAndNull);
258265
this[kState] = createReadableStreamState();
@@ -718,22 +725,8 @@ ObjectDefineProperties(ReadableStream, {
718725
from: kEnumerableProperty,
719726
});
720727

721-
function InternalTransferredReadableStream() {
722-
ObjectSetPrototypeOf(this, ReadableStream.prototype);
723-
markTransferMode(this, false, true);
724-
this[kType] = 'ReadableStream';
725-
this[kState] = createReadableStreamState();
726-
}
727-
728-
ObjectSetPrototypeOf(InternalTransferredReadableStream.prototype, ReadableStream.prototype);
729-
ObjectSetPrototypeOf(InternalTransferredReadableStream, ReadableStream);
730-
731728
function TransferredReadableStream() {
732-
const stream = new InternalTransferredReadableStream();
733-
734-
stream.constructor = ReadableStream;
735-
736-
return stream;
729+
return new ReadableStream(kSkipThrow);
737730
}
738731

739732
TransferredReadableStream.prototype[kDeserialize] = () => {};
@@ -1350,57 +1343,29 @@ ObjectDefineProperties(ReadableByteStreamController.prototype, {
13501343
[SymbolToStringTag]: getNonWritablePropertyDescriptor(ReadableByteStreamController.name),
13511344
});
13521345

1353-
function InternalReadableStream(start, pull, cancel, highWaterMark, size) {
1354-
ObjectSetPrototypeOf(this, ReadableStream.prototype);
1355-
markTransferMode(this, false, true);
1356-
this[kType] = 'ReadableStream';
1357-
this[kState] = createReadableStreamState();
1358-
const controller = new ReadableStreamDefaultController(kSkipThrow);
1346+
function createReadableStream(start, pull, cancel, highWaterMark = 1, size = defaultSizeAlgorithm) {
1347+
const stream = new ReadableStream(kSkipThrow);
13591348
setupReadableStreamDefaultController(
1360-
this,
1361-
controller,
1349+
stream,
1350+
new ReadableStreamDefaultController(kSkipThrow),
13621351
start,
13631352
pull,
13641353
cancel,
13651354
highWaterMark,
13661355
size);
1367-
}
1368-
1369-
ObjectSetPrototypeOf(InternalReadableStream.prototype, ReadableStream.prototype);
1370-
ObjectSetPrototypeOf(InternalReadableStream, ReadableStream);
1371-
1372-
function createReadableStream(start, pull, cancel, highWaterMark = 1, size = defaultSizeAlgorithm) {
1373-
const stream = new InternalReadableStream(start, pull, cancel, highWaterMark, size);
1374-
1375-
// For spec compliance the InternalReadableStream must be a ReadableStream
1376-
stream.constructor = ReadableStream;
13771356
return stream;
13781357
}
13791358

1380-
function InternalReadableByteStream(start, pull, cancel) {
1381-
ObjectSetPrototypeOf(this, ReadableStream.prototype);
1382-
markTransferMode(this, false, true);
1383-
this[kType] = 'ReadableStream';
1384-
this[kState] = createReadableStreamState();
1385-
const controller = new ReadableByteStreamController(kSkipThrow);
1359+
function createReadableByteStream(start, pull, cancel) {
1360+
const stream = new ReadableStream(kSkipThrow);
13861361
setupReadableByteStreamController(
1387-
this,
1388-
controller,
1362+
stream,
1363+
new ReadableByteStreamController(kSkipThrow),
13891364
start,
13901365
pull,
13911366
cancel,
13921367
0,
13931368
undefined);
1394-
}
1395-
1396-
ObjectSetPrototypeOf(InternalReadableByteStream.prototype, ReadableStream.prototype);
1397-
ObjectSetPrototypeOf(InternalReadableByteStream, ReadableStream);
1398-
1399-
function createReadableByteStream(start, pull, cancel) {
1400-
const stream = new InternalReadableByteStream(start, pull, cancel);
1401-
1402-
// For spec compliance the InternalReadableByteStream must be a ReadableStream
1403-
stream.constructor = ReadableStream;
14041369
return stream;
14051370
}
14061371

‎lib/internal/webstreams/writablestream.js‎

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ class WritableStream {
183183
*/
184184
constructor(sink = kEmptyObject, strategy = kEmptyObject) {
185185
markTransferMode(this, false, true);
186+
// Internal construction (transform streams, adapters, transfer):
187+
// the caller sets up the controller, so every WritableStream shares
188+
// one hidden class and no per-instance prototype swap is needed.
189+
if (sink === kSkipThrow) {
190+
this[kState] = createWritableStreamState();
191+
return;
192+
}
186193
validateObject(sink, 'sink', kValidateObjectAllowObjects);
187194
validateObject(strategy, 'strategy', kValidateObjectAllowObjectsAndNull);
188195
const type = sink?.type;
@@ -351,22 +358,8 @@ ObjectDefineProperties(WritableStream.prototype, {
351358
[SymbolToStringTag]: getNonWritablePropertyDescriptor(WritableStream.name),
352359
});
353360

354-
function InternalTransferredWritableStream() {
355-
ObjectSetPrototypeOf(this, WritableStream.prototype);
356-
markTransferMode(this, false, true);
357-
this[kType] = 'WritableStream';
358-
this[kState] = createWritableStreamState();
359-
}
360-
361-
ObjectSetPrototypeOf(InternalTransferredWritableStream.prototype, WritableStream.prototype);
362-
ObjectSetPrototypeOf(InternalTransferredWritableStream, WritableStream);
363-
364361
function TransferredWritableStream() {
365-
const stream = new InternalTransferredWritableStream();
366-
367-
stream.constructor = WritableStream;
368-
369-
return stream;
362+
return new WritableStream(kSkipThrow);
370363
}
371364

372365
TransferredWritableStream.prototype[kDeserialize] = () => {};
@@ -559,33 +552,18 @@ ObjectDefineProperties(WritableStreamDefaultController.prototype, {
559552
[SymbolToStringTag]: getNonWritablePropertyDescriptor(WritableStreamDefaultController.name),
560553
});
561554

562-
function InternalWritableStream(start, write, close, abort, highWaterMark, size) {
563-
ObjectSetPrototypeOf(this, WritableStream.prototype);
564-
markTransferMode(this, false, true);
565-
this[kType] = 'WritableStream';
566-
this[kState] = createWritableStreamState();
567-
568-
const controller = new WritableStreamDefaultController(kSkipThrow);
555+
function createWritableStream(start, write, close, abort, highWaterMark = 1, size = defaultSizeAlgorithm) {
556+
const stream = new WritableStream(kSkipThrow);
569557
setupWritableStreamDefaultController(
570-
this,
571-
controller,
558+
stream,
559+
new WritableStreamDefaultController(kSkipThrow),
572560
start,
573561
write,
574562
close,
575563
abort,
576564
highWaterMark,
577565
size,
578566
);
579-
}
580-
581-
ObjectSetPrototypeOf(InternalWritableStream.prototype, WritableStream.prototype);
582-
ObjectSetPrototypeOf(InternalWritableStream, WritableStream);
583-
584-
function createWritableStream(start, write, close, abort, highWaterMark = 1, size = defaultSizeAlgorithm) {
585-
const stream = new InternalWritableStream(start, write, close, abort, highWaterMark, size);
586-
587-
// For spec compliance the InternalWritableStream must be a WritableStream
588-
stream.constructor = WritableStream;
589567
return stream;
590568
}
591569

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const {
6+
ReadableStream,
7+
WritableStream,
8+
TransformStream,
9+
} = require('stream/web');
10+
11+
// Streams created by internal code paths (transform stream sides, tee
12+
// branches, ReadableStream.from, transferred streams) are plain
13+
// ReadableStream/WritableStream instances: same prototype and no own
14+
// properties beyond what the public constructors create.
15+
16+
function check(stream, Class) {
17+
assert.ok(stream instanceof Class);
18+
assert.strictEqual(Object.getPrototypeOf(stream), Class.prototype);
19+
assert.strictEqual(stream.constructor, Class);
20+
assert.deepStrictEqual(Object.keys(stream), Object.keys(new Class()));
21+
assert.strictEqual(
22+
Object.getOwnPropertyDescriptor(stream, 'constructor'), undefined);
23+
}
24+
25+
{
26+
const { readable, writable } = new TransformStream();
27+
check(readable, ReadableStream);
28+
check(writable, WritableStream);
29+
}
30+
31+
{
32+
const [branch1, branch2] = new ReadableStream().tee();
33+
check(branch1, ReadableStream);
34+
check(branch2, ReadableStream);
35+
}
36+
37+
{
38+
const [branch1, branch2] = new ReadableStream({ type: 'bytes' }).tee();
39+
check(branch1, ReadableStream);
40+
check(branch2, ReadableStream);
41+
}
42+
43+
check(ReadableStream.from([]), ReadableStream);
44+
45+
{
46+
const readable = new ReadableStream();
47+
const writable = new WritableStream();
48+
const transferred = structuredClone(
49+
{ readable, writable },
50+
{ transfer: [readable, writable] });
51+
check(transferred.readable, ReadableStream);
52+
check(transferred.writable, WritableStream);
53+
}

0 commit comments

Comments
 (0)