diff --git a/interfaces/streams.idl b/interfaces/streams.idl new file mode 100644 index 00000000000000..9135126cf03798 --- /dev/null +++ b/interfaces/streams.idl @@ -0,0 +1,204 @@ +[Exposed=(Window,Worker,Worklet)] +interface ReadableStream { + constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); + + readonly attribute boolean locked; + + Promise cancel(optional any reason); + ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {}); + ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); + Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); + sequence tee(); + + async iterable(optional ReadableStreamIteratorOptions options = {}); +}; + +typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader; + +enum ReadableStreamReaderMode { "byob" }; + +dictionary ReadableStreamGetReaderOptions { + ReadableStreamReaderMode mode; +}; + +dictionary ReadableStreamIteratorOptions { + boolean preventCancel = false; +}; + +dictionary ReadableWritablePair { + required ReadableStream readable; + required WritableStream writable; +}; + +dictionary StreamPipeOptions { + boolean preventClose = false; + boolean preventAbort = false; + boolean preventCancel = false; + AbortSignal signal; +}; + +dictionary UnderlyingSource { + UnderlyingSourceStartCallback start; + UnderlyingSourcePullCallback pull; + UnderlyingSourceCancelCallback cancel; + ReadableStreamType type; + [EnforceRange] unsigned long long autoAllocateChunkSize; +}; + +typedef (ReadableStreamDefaultController or ReadableByteStreamController) ReadableStreamController; + +callback UnderlyingSourceStartCallback = any (ReadableStreamController controller); +callback UnderlyingSourcePullCallback = Promise (ReadableStreamController controller); +callback UnderlyingSourceCancelCallback = Promise (optional any reason); + +enum ReadableStreamType { "bytes" }; + +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamDefaultReader { + constructor(ReadableStream stream); + + readonly attribute Promise closed; + + Promise cancel(optional any reason); + Promise read(); + void releaseLock(); +}; + +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamBYOBReader { + constructor(ReadableStream stream); + + readonly attribute Promise closed; + + Promise cancel(optional any reason); + Promise read(ArrayBufferView view); + void releaseLock(); +}; + +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + + void close(); + void enqueue(optional any chunk); + void error(optional any e); +}; + +[Exposed=(Window,Worker,Worklet)] +interface ReadableByteStreamController { + readonly attribute ReadableStreamBYOBRequest? byobRequest; + readonly attribute unrestricted double? desiredSize; + + void close(); + void enqueue(ArrayBufferView chunk); + void error(optional any e); +}; + +[Exposed=(Window,Worker,Worklet)] +interface ReadableStreamBYOBRequest { + readonly attribute ArrayBufferView? view; + + void respond([EnforceRange] unsigned long long bytesWritten); + void respondWithNewView(ArrayBufferView view); +}; + +[Exposed=(Window,Worker,Worklet)] +interface WritableStream { + constructor(optional object underlyingSink, optional QueuingStrategy strategy = {}); + + readonly attribute boolean locked; + + Promise abort(optional any reason); + Promise close(); + WritableStreamDefaultWriter getWriter(); +}; + +dictionary UnderlyingSink { + UnderlyingSinkStartCallback start; + UnderlyingSinkWriteCallback write; + UnderlyingSinkCloseCallback close; + UnderlyingSinkAbortCallback abort; + any type; +}; + +callback UnderlyingSinkStartCallback = any (WritableStreamDefaultController controller); +callback UnderlyingSinkWriteCallback = Promise (WritableStreamDefaultController controller, optional any chunk); +callback UnderlyingSinkCloseCallback = Promise (); +callback UnderlyingSinkAbortCallback = Promise (optional any reason); + +[Exposed=(Window,Worker,Worklet)] +interface WritableStreamDefaultWriter { + constructor(WritableStream stream); + + readonly attribute Promise closed; + readonly attribute unrestricted double? desiredSize; + readonly attribute Promise ready; + + Promise abort(optional any reason); + Promise close(); + void releaseLock(); + Promise write(optional any chunk); +}; + +[Exposed=(Window,Worker,Worklet)] +interface WritableStreamDefaultController { + void error(optional any e); +}; + +[Exposed=(Window,Worker,Worklet)] +interface TransformStream { + constructor(optional object transformer, + optional QueuingStrategy writableStrategy = {}, + optional QueuingStrategy readableStrategy = {}); + + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; +}; + +dictionary Transformer { + TransformerStartCallback start; + TransformerTransformCallback transform; + TransformerFlushCallback flush; + any readableType; + any writableType; +}; + +callback TransformerStartCallback = any (TransformStreamDefaultController controller); +callback TransformerFlushCallback = Promise (TransformStreamDefaultController controller); +callback TransformerTransformCallback = Promise (TransformStreamDefaultController controller, optional any chunk); + +[Exposed=(Window,Worker,Worklet)] +interface TransformStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + + void enqueue(optional any chunk); + void error(optional any reason); + void terminate(); +}; + +dictionary QueuingStrategy { + unrestricted double highWaterMark; + QueuingStrategySize size; +}; + +callback QueuingStrategySize = unrestricted double (optional any chunk); + +dictionary QueuingStrategyInit { + required unrestricted double highWaterMark; +}; + +[Exposed=(Window,Worker,Worklet)] +interface ByteLengthQueuingStrategy { + constructor(QueuingStrategyInit init); + + attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; + +[Exposed=(Window,Worker,Worklet)] +interface CountQueuingStrategy { + constructor(QueuingStrategyInit init); + + readonly attribute unrestricted double highWaterMark; + readonly attribute Function size; +}; diff --git a/streams/byte-length-queuing-strategy.any.js b/streams/byte-length-queuing-strategy.any.js deleted file mode 100644 index 257cec804fa8d5..00000000000000 --- a/streams/byte-length-queuing-strategy.any.js +++ /dev/null @@ -1,132 +0,0 @@ -// META: global=window,worker,jsshell -'use strict'; - -test(() => { - - new ByteLengthQueuingStrategy({ highWaterMark: 4 }); - -}, 'Can construct a ByteLengthQueuingStrategy with a valid high water mark'); - -test(() => { - - for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) { - const strategy = new ByteLengthQueuingStrategy({ highWaterMark }); - assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`); - } - -}, 'Can construct a ByteLengthQueuingStrategy with any value as its high water mark'); - -test(() => { - - const highWaterMark = 1; - const highWaterMarkObjectGetter = { - get highWaterMark() { return highWaterMark; } - }; - const error = new Error('wow!'); - const highWaterMarkObjectGetterThrowing = { - get highWaterMark() { throw error; } - }; - - assert_throws_js(TypeError, () => new ByteLengthQueuingStrategy(), 'construction fails with undefined'); - assert_throws_js(TypeError, () => new ByteLengthQueuingStrategy(null), 'construction fails with null'); - assert_throws_js(Error, () => new ByteLengthQueuingStrategy(highWaterMarkObjectGetterThrowing), - 'construction fails with an object with a throwing highWaterMark getter'); - - // Should not fail: - new ByteLengthQueuingStrategy('potato'); - new ByteLengthQueuingStrategy({}); - new ByteLengthQueuingStrategy(highWaterMarkObjectGetter); - -}, 'ByteLengthQueuingStrategy constructor behaves as expected with strange arguments'); - -test(() => { - - const size = 1024; - const chunk = { byteLength: size }; - const chunkGetter = { - get byteLength() { return size; } - }; - const error = new Error('wow!'); - const chunkGetterThrowing = { - get byteLength() { throw error; } - }; - assert_throws_js(TypeError, () => ByteLengthQueuingStrategy.prototype.size(), 'size fails with undefined'); - assert_throws_js(TypeError, () => ByteLengthQueuingStrategy.prototype.size(null), 'size fails with null'); - assert_equals(ByteLengthQueuingStrategy.prototype.size('potato'), undefined, - 'size succeeds with undefined with a random non-object type'); - assert_equals(ByteLengthQueuingStrategy.prototype.size({}), undefined, - 'size succeeds with undefined with an object without hwm property'); - assert_equals(ByteLengthQueuingStrategy.prototype.size(chunk), size, - 'size succeeds with the right amount with an object with a hwm'); - assert_equals(ByteLengthQueuingStrategy.prototype.size(chunkGetter), size, - 'size succeeds with the right amount with an object with a hwm getter'); - assert_throws_js(Error, () => ByteLengthQueuingStrategy.prototype.size(chunkGetterThrowing), - 'size fails with the error thrown by the getter'); - -}, 'ByteLengthQueuingStrategy size behaves as expected with strange arguments'); - -test(() => { - - const thisValue = null; - const returnValue = { 'returned from': 'byteLength getter' }; - const chunk = { - get byteLength() { return returnValue; } - }; - - assert_equals(ByteLengthQueuingStrategy.prototype.size.call(thisValue, chunk), returnValue); - -}, 'ByteLengthQueuingStrategy.prototype.size should work generically on its this and its arguments'); - -test(() => { - - const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 }); - - assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'), - { value: 4, writable: true, enumerable: true, configurable: true }, - 'highWaterMark property should be a data property with the value passed the constructor'); - assert_equals(typeof strategy.size, 'function'); - -}, 'ByteLengthQueuingStrategy instances have the correct properties'); - -test(() => { - - const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 }); - assert_equals(strategy.highWaterMark, 4); - - strategy.highWaterMark = 10; - assert_equals(strategy.highWaterMark, 10); - - strategy.highWaterMark = 'banana'; - assert_equals(strategy.highWaterMark, 'banana'); - -}, 'ByteLengthQueuingStrategy\'s highWaterMark property can be set to anything'); - -test(() => { - - assert_equals(ByteLengthQueuingStrategy.name, 'ByteLengthQueuingStrategy', - 'ByteLengthQueuingStrategy.name must be "ByteLengthQueuingStrategy"'); - -}, 'ByteLengthQueuingStrategy.name is correct'); - -class SubClass extends ByteLengthQueuingStrategy { - size() { - return 2; - } - - subClassMethod() { - return true; - } -} - -test(() => { - - const sc = new SubClass({highWaterMark: 77}); - assert_equals(sc.constructor.name, 'SubClass', - 'constructor.name should be correct'); - assert_equals(sc.highWaterMark, 77, - 'highWaterMark should come from the parent class'); - assert_equals(sc.size(), 2, - 'size() on the subclass should override the parent'); - assert_true(sc.subClassMethod(), 'subClassMethod() should work'); - -}, 'subclassing ByteLengthQueuingStrategy should work correctly'); diff --git a/streams/count-queuing-strategy.any.js b/streams/count-queuing-strategy.any.js deleted file mode 100644 index 8b37cd059a1671..00000000000000 --- a/streams/count-queuing-strategy.any.js +++ /dev/null @@ -1,131 +0,0 @@ -// META: global=window,worker,jsshell -'use strict'; - -test(() => { - - new CountQueuingStrategy({ highWaterMark: 4 }); - -}, 'Can construct a CountQueuingStrategy with a valid high water mark'); - -test(() => { - - for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) { - const strategy = new CountQueuingStrategy({ highWaterMark }); - assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`); - } - -}, 'Can construct a CountQueuingStrategy with any value as its high water mark'); - -test(() => { - - const highWaterMark = 1; - const highWaterMarkObjectGetter = { - get highWaterMark() { return highWaterMark; } - }; - const error = new Error('wow!'); - const highWaterMarkObjectGetterThrowing = { - get highWaterMark() { throw error; } - }; - - assert_throws_js(TypeError, () => new CountQueuingStrategy(), 'construction fails with undefined'); - assert_throws_js(TypeError, () => new CountQueuingStrategy(null), 'construction fails with null'); - assert_throws_js(Error, () => new CountQueuingStrategy(highWaterMarkObjectGetterThrowing), - 'construction fails with an object with a throwing highWaterMark getter'); - - // Should not fail: - new CountQueuingStrategy('potato'); - new CountQueuingStrategy({}); - new CountQueuingStrategy(highWaterMarkObjectGetter); - -}, 'CountQueuingStrategy constructor behaves as expected with strange arguments'); - - -test(() => { - - const thisValue = null; - const chunk = { - get byteLength() { - throw new TypeError('shouldn\'t be called'); - } - }; - - assert_equals(CountQueuingStrategy.prototype.size.call(thisValue, chunk), 1); - -}, 'CountQueuingStrategy.prototype.size should work generically on its this and its arguments'); - -test(() => { - - const size = 1024; - const chunk = { byteLength: size }; - const chunkGetter = { - get byteLength() { return size; } - }; - const error = new Error('wow!'); - const chunkGetterThrowing = { - get byteLength() { throw error; } - }; - - assert_equals(CountQueuingStrategy.prototype.size(), 1, 'size returns 1 with undefined'); - assert_equals(CountQueuingStrategy.prototype.size(null), 1, 'size returns 1 with null'); - assert_equals(CountQueuingStrategy.prototype.size('potato'), 1, 'size returns 1 with non-object type'); - assert_equals(CountQueuingStrategy.prototype.size({}), 1, 'size returns 1 with empty object'); - assert_equals(CountQueuingStrategy.prototype.size(chunk), 1, 'size returns 1 with a chunk'); - assert_equals(CountQueuingStrategy.prototype.size(chunkGetter), 1, 'size returns 1 with chunk getter'); - assert_equals(CountQueuingStrategy.prototype.size(chunkGetterThrowing), 1, - 'size returns 1 with chunk getter that throws'); - -}, 'CountQueuingStrategy size behaves as expected with strange arguments'); - -test(() => { - - const strategy = new CountQueuingStrategy({ highWaterMark: 4 }); - - assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'), - { value: 4, writable: true, enumerable: true, configurable: true }, - 'highWaterMark property should be a data property with the value passed the constructor'); - assert_equals(typeof strategy.size, 'function'); - -}, 'CountQueuingStrategy instances have the correct properties'); - -test(() => { - - const strategy = new CountQueuingStrategy({ highWaterMark: 4 }); - assert_equals(strategy.highWaterMark, 4); - - strategy.highWaterMark = 10; - assert_equals(strategy.highWaterMark, 10); - - strategy.highWaterMark = 'banana'; - assert_equals(strategy.highWaterMark, 'banana'); - -}, 'CountQueuingStrategy\'s highWaterMark property can be set to anything'); - -test(() => { - - assert_equals(CountQueuingStrategy.name, 'CountQueuingStrategy', - 'CountQueuingStrategy.name must be "CountQueuingStrategy"'); - -}, 'CountQueuingStrategy.name is correct'); - -class SubClass extends CountQueuingStrategy { - size() { - return 2; - } - - subClassMethod() { - return true; - } -} - -test(() => { - - const sc = new SubClass({highWaterMark: 77}); - assert_equals(sc.constructor.name, 'SubClass', - 'constructor.name should be correct'); - assert_equals(sc.highWaterMark, 77, - 'highWaterMark should come from the parent class'); - assert_equals(sc.size(), 2, - 'size() on the subclass should override the parent'); - assert_true(sc.subClassMethod(), 'subClassMethod() should work'); - -}, 'subclassing CountQueuingStrategy should work correctly'); diff --git a/streams/idlharness.any.js b/streams/idlharness.any.js new file mode 100644 index 00000000000000..e9f085bfc0e11b --- /dev/null +++ b/streams/idlharness.any.js @@ -0,0 +1,75 @@ +// META: global=window,worker +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +idl_test( + ['streams'], + ['dom'], // for AbortSignal + async idl_array => { + // Empty try/catches ensure that if something isn't implemented (e.g., readable byte streams, or writable streams) + // the harness still sets things up correctly. Note that the corresponding interface tests will still fail. + + try { + new ReadableStream({ + start(c) { + self.readableStreamDefaultController = c; + } + }); + } catch {} + + try { + new ReadableStream({ + start(c) { + self.readableByteStreamController = c; + }, + type: 'bytes' + }); + } catch {} + + try { + const stream = new ReadableStream({ + pull() { + self.readableStreamByobRequest = controller.byobRequest; + }, + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + + await reader.read(new Uint8Array(1)); + } catch {} + + try { + new WritableStream({ + start(c) { + self.writableStreamDefaultController = c; + } + }); + } catch {} + + try { + new TransformStream({ + start(c) { + self.transformStreamDefaultController = c; + } + }); + } catch {} + + idl_array.add_objects({ + ReadableStream: ["new ReadableStream()"], + ReadableStreamDefaultReader: ["(new ReadableStream()).getReader()"], + ReadableStreamBYOBReader: ["(new ReadableStream({ type: 'bytes' })).getReader('byob')"], + ReadableStreamDefaultController: ["self.readableStreamDefaultController"], + ReadableByteStreamController: ["self.readableByteStreamController"], + ReadableStreamBYOBRequest: ["self.readableStreamByobRequest"], + WritableStream: ["new WritableStream()"], + WritableStreamDefaultWriter: ["(new WritableStream()).getWriter()"], + WritableStreamDefaultController: ["self.writableStreamDefaultController"], + TransformStream: ["new TransformStream()"], + TransformStreamDefaultController: ["self.transformStreamDefaultController"], + ByteLengthQueuingStrategy: ["new ByteLengthQueuingStrategy({ highWaterMark: 5 })"], + CountQueuingStrategy: ["new CountQueuingStrategy({ highWaterMark: 5 })"] + }); + } +); diff --git a/streams/piping/flow-control.any.js b/streams/piping/flow-control.any.js index 2127c6e8381c18..db83c011f4a718 100644 --- a/streams/piping/flow-control.any.js +++ b/streams/piping/flow-control.any.js @@ -81,12 +81,8 @@ promise_test(() => { const rs = recordingReadableStream(); - const startPromise = Promise.resolve(); let resolveWritePromise; const ws = recordingWritableStream({ - start() { - return startPromise; - }, write() { if (!resolveWritePromise) { // first write @@ -101,7 +97,7 @@ promise_test(() => { const writer = ws.getWriter(); writer.write('a'); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { assert_array_equals(ws.events, ['write', 'a']); assert_equals(writer.desiredSize, 0, 'after writing the writer\'s desiredSize must be 0'); writer.releaseLock(); diff --git a/streams/piping/pipe-through.any.js b/streams/piping/pipe-through.any.js index 29e5c06e676931..35dbb456b3e2c6 100644 --- a/streams/piping/pipe-through.any.js +++ b/streams/piping/pipe-through.any.js @@ -106,17 +106,19 @@ for (const readable of badReadables) { test(() => { const rs = new ReadableStream(); - const writable = new WritableStream(); let writableGetterCalled = false; - assert_throws_js(TypeError, () => rs.pipeThrough({ - get writable() { - writableGetterCalled = true; - return new WritableStream(); - }, - readable - }), - 'pipeThrough should brand-check readable'); - assert_true(writableGetterCalled, 'writable should have been accessed'); + assert_throws_js( + TypeError, + () => rs.pipeThrough({ + get writable() { + writableGetterCalled = true; + return new WritableStream(); + }, + readable + }), + 'pipeThrough should brand-check readable' + ); + assert_false(writableGetterCalled, 'writable should not have been accessed'); }, `pipeThrough should brand-check readable and not allow '${readable}'`); } @@ -150,7 +152,7 @@ test(t => { }, { highWaterMark: 0 }); const throwingWritable = { - readable: {}, + readable: rs, get writable() { throw error; } diff --git a/streams/piping/throwing-options.any.js b/streams/piping/throwing-options.any.js index 1b6add3f7436ca..bc1cf328da61e6 100644 --- a/streams/piping/throwing-options.any.js +++ b/streams/piping/throwing-options.any.js @@ -1,4 +1,5 @@ // META: global=window,worker,jsshell +'use strict'; class ThrowingOptions { constructor(whatShouldThrow) { @@ -34,7 +35,7 @@ class ThrowingOptions { } } -const checkOrder = ['preventClose', 'preventAbort', 'preventCancel', 'signal']; +const checkOrder = ['preventAbort', 'preventCancel', 'preventClose', 'signal']; for (let i = 0; i < checkOrder.length; ++i) { const whatShouldThrow = checkOrder[i]; diff --git a/streams/queuing-strategies.any.js b/streams/queuing-strategies.any.js new file mode 100644 index 00000000000000..1846ea63e35459 --- /dev/null +++ b/streams/queuing-strategies.any.js @@ -0,0 +1,135 @@ +// META: global=window,worker,jsshell +'use strict'; + +const highWaterMarkConversions = new Map([ + [-Infinity, -Infinity], + [-5, -5], + [false, 0], + [true, 1], + [NaN, NaN], + ['foo', NaN], + ['0', 0], + [{}, NaN], + [() => {}, NaN] +]); + +for (const QueuingStrategy of [CountQueuingStrategy, ByteLengthQueuingStrategy]) { + test(() => { + new QueuingStrategy({ highWaterMark: 4 }); + }, `${QueuingStrategy.name}: Can construct a with a valid high water mark`); + + test(() => { + const highWaterMark = 1; + const highWaterMarkObjectGetter = { + get highWaterMark() { return highWaterMark; } + }; + const error = new Error('wow!'); + const highWaterMarkObjectGetterThrowing = { + get highWaterMark() { throw error; } + }; + + assert_throws_js(TypeError, () => new QueuingStrategy(), 'construction fails with undefined'); + assert_throws_js(TypeError, () => new QueuingStrategy(null), 'construction fails with null'); + assert_throws_js(TypeError, () => new QueuingStrategy(true), 'construction fails with true'); + assert_throws_js(TypeError, () => new QueuingStrategy(5), 'construction fails with 5'); + assert_throws_js(TypeError, () => new QueuingStrategy({}), 'construction fails with {}'); + assert_throws_exactly(error, () => new QueuingStrategy(highWaterMarkObjectGetterThrowing), + 'construction fails with an object with a throwing highWaterMark getter'); + + assert_equals((new QueuingStrategy(highWaterMarkObjectGetter)).highWaterMark, highWaterMark); + }, `${QueuingStrategy.name}: Constructor behaves as expected with strange arguments`); + + test(() => { + for (const [input, output] of highWaterMarkConversions.entries()) { + const strategy = new QueuingStrategy({ highWaterMark: input }); + assert_equals(strategy.highWaterMark, output, `${input} gets set correctly`); + } + }, `${QueuingStrategy.name}: highWaterMark constructor values are converted per the unrestricted double rules`); + + test(() => { + const size1 = (new QueuingStrategy({ highWaterMark: 5 })).size; + const size2 = (new QueuingStrategy({ highWaterMark: 10 })).size; + + assert_equals(size1, size2); + }, `${QueuingStrategy.name}: size is the same function across all instances`); + + test(() => { + const size = (new QueuingStrategy({ highWaterMark: 5 })).size; + assert_equals(size.name, 'size'); + }, `${QueuingStrategy.name}: size should have the right name`); + + test(() => { + class SubClass extends QueuingStrategy { + size() { + return 2; + } + + subClassMethod() { + return true; + } + } + + const sc = new SubClass({ highWaterMark: 77 }); + assert_equals(sc.constructor.name, 'SubClass', 'constructor.name should be correct'); + assert_equals(sc.highWaterMark, 77, 'highWaterMark should come from the parent class'); + assert_equals(sc.size(), 2, 'size() on the subclass should override the parent'); + assert_true(sc.subClassMethod(), 'subClassMethod() should work'); + }, `${QueuingStrategy.name}: subclassing should work correctly`); +} + +test(() => { + const size = (new CountQueuingStrategy({ highWaterMark: 5 })).size; + assert_equals(size.length, 0); +}, 'CountQueuingStrategy: size should have the right length'); + +test(() => { + const size = (new ByteLengthQueuingStrategy({ highWaterMark: 5 })).size; + assert_equals(size.length, 1); +}, 'ByteLengthQueuingStrategy: size should have the right length'); + +test(() => { + const size = 1024; + const chunk = { byteLength: size }; + const chunkGetter = { + get byteLength() { return size; } + }; + const error = new Error('wow!'); + const chunkGetterThrowing = { + get byteLength() { throw error; } + }; + + const sizeFunction = (new CountQueuingStrategy({ highWaterMark: 5 })).size; + + assert_equals(sizeFunction(), 1, 'size returns 1 with undefined'); + assert_equals(sizeFunction(null), 1, 'size returns 1 with null'); + assert_equals(sizeFunction('potato'), 1, 'size returns 1 with non-object type'); + assert_equals(sizeFunction({}), 1, 'size returns 1 with empty object'); + assert_equals(sizeFunction(chunk), 1, 'size returns 1 with a chunk'); + assert_equals(sizeFunction(chunkGetter), 1, 'size returns 1 with chunk getter'); + assert_equals(sizeFunction(chunkGetterThrowing), 1, + 'size returns 1 with chunk getter that throws'); +}, 'CountQueuingStrategy: size behaves as expected with strange arguments'); + +test(() => { + const size = 1024; + const chunk = { byteLength: size }; + const chunkGetter = { + get byteLength() { return size; } + }; + const error = new Error('wow!'); + const chunkGetterThrowing = { + get byteLength() { throw error; } + }; + + const sizeFunction = (new ByteLengthQueuingStrategy({ highWaterMark: 5 })).size; + + assert_throws_js(TypeError, () => sizeFunction(), 'size fails with undefined'); + assert_throws_js(TypeError, () => sizeFunction(null), 'size fails with null'); + assert_equals(sizeFunction('potato'), undefined, 'size succeeds with undefined with a random non-object type'); + assert_equals(sizeFunction({}), undefined, 'size succeeds with undefined with an object without hwm property'); + assert_equals(sizeFunction(chunk), size, 'size succeeds with the right amount with an object with a hwm'); + assert_equals(sizeFunction(chunkGetter), size, + 'size succeeds with the right amount with an object with a hwm getter'); + assert_throws_exactly(error, () => sizeFunction(chunkGetterThrowing), + 'size fails with the error thrown by the getter'); +}, 'ByteLengthQueuingStrategy: size behaves as expected with strange arguments'); diff --git a/streams/readable-byte-streams/detached-buffers.any.js b/streams/readable-byte-streams/bad-buffers-and-views.any.js similarity index 58% rename from streams/readable-byte-streams/detached-buffers.any.js rename to streams/readable-byte-streams/bad-buffers-and-views.any.js index 5df85ccf8dcc73..0777208da44b29 100644 --- a/streams/readable-byte-streams/detached-buffers.any.js +++ b/streams/readable-byte-streams/bad-buffers-and-views.any.js @@ -48,16 +48,36 @@ promise_test(() => { }, 'ReadableStream with byte source: read()ing from a stream with queued chunks still transfers the buffer'); test(() => { - const stream = new ReadableStream({ + new ReadableStream({ start(c) { const view = new Uint8Array([1, 2, 3]); c.enqueue(view); - assert_throws_js(TypeError, () => c.enqueue(view), 'enqueuing an already-detached buffer must throw'); + assert_throws_js(TypeError, () => c.enqueue(view)); }, type: 'bytes' }); }, 'ReadableStream with byte source: enqueuing an already-detached buffer throws'); +test(() => { + new ReadableStream({ + start(c) { + const view = new Uint8Array([]); + assert_throws_js(TypeError, () => c.enqueue(view)); + }, + type: 'bytes' + }); +}, 'ReadableStream with byte source: enqueuing a zero-length buffer throws'); + +test(() => { + new ReadableStream({ + start(c) { + const view = new Uint8Array(new ArrayBuffer(10), 0, 0); + assert_throws_js(TypeError, () => c.enqueue(view)); + }, + type: 'bytes' + }); +}, 'ReadableStream with byte source: enqueuing a zero-length view on a non-zero-length buffer throws'); + promise_test(t => { const stream = new ReadableStream({ start(c) { @@ -70,11 +90,36 @@ promise_test(t => { const view = new Uint8Array([4, 5, 6]); return reader.read(view).then(() => { // view is now detached - return promise_rejects_js(t, TypeError, reader.read(view), - 'read(view) must reject when given an already-detached buffer'); + return promise_rejects_js(t, TypeError, reader.read(view)); }); }, 'ReadableStream with byte source: reading into an already-detached buffer rejects'); +promise_test(t => { + const stream = new ReadableStream({ + start(c) { + c.enqueue(new Uint8Array([1, 2, 3])); + }, + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + const view = new Uint8Array(); + return promise_rejects_js(t, TypeError, reader.read(view)); +}, 'ReadableStream with byte source: reading into a zero-length buffer rejects'); + +promise_test(t => { + const stream = new ReadableStream({ + start(c) { + c.enqueue(new Uint8Array([1, 2, 3])); + }, + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + const view = new Uint8Array(new ArrayBuffer(10), 0, 0); + return promise_rejects_js(t, TypeError, reader.read(view)); +}, 'ReadableStream with byte source: reading into a zero-length view on a non-zero-length buffer rejects'); + async_test(t => { const stream = new ReadableStream({ pull: t.step_func_done(c => { @@ -118,8 +163,7 @@ async_test(t => { const view = new Uint8Array([1, 2, 3]); reader.read(view); - assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view), - 'respondWithNewView() must throw if passed a detached view'); + assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view)); }), type: 'bytes' }); @@ -129,6 +173,36 @@ async_test(t => { }, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' + '(in the readable state)'); +async_test(t => { + const stream = new ReadableStream({ + pull: t.step_func_done(c => { + const view = new Uint8Array(); + + assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view)); + }), + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + reader.read(new Uint8Array([4, 5, 6])); +}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' + + '(in the readable state)'); + +async_test(t => { + const stream = new ReadableStream({ + pull: t.step_func_done(c => { + const view = new Uint8Array(new ArrayBuffer(10), 0, 0); + + assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view)); + }), + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + reader.read(new Uint8Array([4, 5, 6])); +}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is zero-length on a ' + + 'non-zero-length buffer (in the readable state)'); + async_test(t => { const stream = new ReadableStream({ pull: t.step_func_done(c => { @@ -139,8 +213,7 @@ async_test(t => { c.close(); const zeroLengthView = new Uint8Array(view.buffer, 0, 0); - assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(zeroLengthView), - 'respondWithNewView() must throw if passed a (zero-length) view whose buffer has been detached'); + assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(zeroLengthView)); }), type: 'bytes' }); @@ -149,3 +222,37 @@ async_test(t => { reader.read(new Uint8Array([4, 5, 6])); }, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' + '(in the closed state)'); + +async_test(t => { + const stream = new ReadableStream({ + pull: t.step_func_done(c => { + const view = new Uint8Array(); + + c.close(); + + assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view)); + }), + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + reader.read(new Uint8Array([4, 5, 6])); +}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' + + '(in the closed state)'); + +async_test(t => { + const stream = new ReadableStream({ + pull: t.step_func_done(c => { + const view = new Uint8Array(new ArrayBuffer(10), 0, 0); + + c.close(); + + assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view)); + }), + type: 'bytes' + }); + const reader = stream.getReader({ mode: 'byob' }); + + reader.read(new Uint8Array([4, 5, 6])); +}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is zero-length on a ' + + 'non-zero-length buffer (in the closed state)'); diff --git a/streams/readable-byte-streams/brand-checks.any.js b/streams/readable-byte-streams/brand-checks.any.js deleted file mode 100644 index c518f0881bccee..00000000000000 --- a/streams/readable-byte-streams/brand-checks.any.js +++ /dev/null @@ -1,189 +0,0 @@ -// META: global=window,worker,jsshell -// META: script=../resources/test-utils.js -'use strict'; - -let ReadableStreamBYOBReader; -let ReadableByteStreamController; - -test(() => { - - // It's not exposed globally, but we test a few of its properties here. - ReadableStreamBYOBReader = realRSBYOBReader().constructor; - - assert_equals(ReadableStreamBYOBReader.name, 'ReadableStreamBYOBReader', 'ReadableStreamBYOBReader should be set'); - -}, 'Can get the ReadableStreamBYOBReader constructor indirectly'); - -test(() => { - - // It's not exposed globally, but we test a few of its properties here. - ReadableByteStreamController = realRBSController().constructor; - - assert_equals(ReadableByteStreamController.name, 'ReadableByteStreamController', - 'ReadableByteStreamController should be set'); - -}, 'Can get the ReadableByteStreamController constructor indirectly'); - -function fakeRS() { - return Object.setPrototypeOf({ - cancel() { return Promise.resolve(); }, - getReader() { return realRSBYOBReader(); }, - pipeThrough(obj) { return obj.readable; }, - pipeTo() { return Promise.resolve(); }, - tee() { return [realRS(), realRS()]; } - }, ReadableStream.prototype); -} - -function realRS() { - return new ReadableStream(); -} - -function fakeRSBYOBReader() { - return Object.setPrototypeOf({ - get closed() { return Promise.resolve(); }, - cancel() { return Promise.resolve(); }, - read() { return Promise.resolve({ value: undefined, done: true }); }, - releaseLock() { return; } - }, ReadableStreamBYOBReader.prototype); -} - -function realRSBYOBReader() { - return new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }); -} - -function fakeRBSController() { - return Object.setPrototypeOf({ - close() { }, - enqueue() { }, - error() { } - }, ReadableByteStreamController.prototype); -} - -function realRBSController() { - let controller; - new ReadableStream({ - start(c) { - controller = c; - }, - type: 'bytes' - }); - return controller; -} - -test(() => { - - assert_throws_js(TypeError, () => new ReadableStreamBYOBReader(fakeRS()), 'constructor should throw'); - -}, 'ReadableStreamBYOBReader enforces a brand check on its argument'); - -promise_test(t => { - - return getterRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'closed', - [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]); - -}, 'ReadableStreamBYOBReader.prototype.closed enforces a brand check'); - -promise_test(t => { - - return methodRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'cancel', - [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]); - -}, 'ReadableStreamBYOBReader.prototype.cancel enforces a brand check'); - -promise_test(t => { - - return methodRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'read', - [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null], [new Uint8Array(1)]); - -}, 'ReadableStreamBYOBReader.prototype.read enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStreamBYOBReader.prototype, 'releaseLock', - [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]); - -}, 'ReadableStreamBYOBReader.prototype.releaseLock enforces a brand check'); - -test(() => { - - assert_throws_js(TypeError, () => new ReadableByteStreamController(fakeRS()), - 'Constructing a ReadableByteStreamController should throw'); - -}, 'ReadableByteStreamController enforces a brand check on its arguments'); - -test(() => { - - assert_throws_js(TypeError, () => new ReadableByteStreamController(realRS()), - 'Constructing a ReadableByteStreamController should throw'); - -}, 'ReadableByteStreamController can\'t be given a fully-constructed ReadableStream'); - -test(() => { - - getterThrowsForAll(ReadableByteStreamController.prototype, 'byobRequest', - [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]); - -}, 'ReadableByteStreamController.prototype.byobRequest enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableByteStreamController.prototype, 'close', - [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]); - -}, 'ReadableByteStreamController.prototype.close enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableByteStreamController.prototype, 'enqueue', - [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null], [new Uint8Array(1)]); - -}, 'ReadableByteStreamController.prototype.enqueue enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableByteStreamController.prototype, 'error', - [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]); - -}, 'ReadableByteStreamController.prototype.error enforces a brand check'); - -// ReadableStreamBYOBRequest can only be accessed asynchronously, so cram everything into one test. -promise_test(t => { - - let ReadableStreamBYOBRequest; - const rs = new ReadableStream({ - pull(controller) { - return t.step(() => { - const byobRequest = controller.byobRequest; - ReadableStreamBYOBRequest = byobRequest.constructor; - brandChecks(); - byobRequest.respond(1); - }); - }, - type: 'bytes' - }); - const reader = rs.getReader({ mode: 'byob' }); - return reader.read(new Uint8Array(1)); - - function fakeRSBYOBRequest() { - return Object.setPrototypeOf({ - get view() {}, - respond() {}, - respondWithNewView() {} - }, ReadableStreamBYOBRequest.prototype); - } - - function brandChecks() { - for (const badController of [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]) { - assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(badController, new Uint8Array(1)), - 'ReadableStreamBYOBRequest constructor must throw for an invalid controller argument'); - } - getterThrowsForAll(ReadableStreamBYOBRequest.prototype, 'view', - [fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null]); - methodThrowsForAll(ReadableStreamBYOBRequest.prototype, 'respond', - [fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null], [1]); - methodThrowsForAll(ReadableStreamBYOBRequest.prototype, 'respondWithNewView', - [fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null], - [new Uint8Array(1)]); - } - -}, 'ReadableStreamBYOBRequest enforces brand checks'); diff --git a/streams/readable-byte-streams/construct-byob-request.any.js b/streams/readable-byte-streams/construct-byob-request.any.js index 95e736434cfbae..1386d84599a4cd 100644 --- a/streams/readable-byte-streams/construct-byob-request.any.js +++ b/streams/readable-byte-streams/construct-byob-request.any.js @@ -17,8 +17,6 @@ function getRealByteStreamController() { return controller; } -const ReadableByteStreamController = getRealByteStreamController().constructor; - // Create an object pretending to have prototype |prototype|, of type |type|. |type| is one of "undefined", "null", // "fake", or "real". "real" will call the realObjectCreator function to get a real instance of the object. function createDummyObject(prototype, type, realObjectCreator) { @@ -41,39 +39,15 @@ function createDummyObject(prototype, type, realObjectCreator) { const dummyTypes = ['undefined', 'null', 'fake', 'real']; -function runTests(ReadableStreamBYOBRequest) { - for (const controllerType of dummyTypes) { - const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType, - getRealByteStreamController); - for (const viewType of dummyTypes) { - const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16)); - test(() => { - assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(controller, view), - 'constructor should throw'); - }, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` + - `ReadableByteStreamController and a ${viewType} view`); - } +for (const controllerType of dummyTypes) { + const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType, + getRealByteStreamController); + for (const viewType of dummyTypes) { + const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16)); + test(() => { + assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(controller, view), + 'constructor should throw'); + }, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` + + `ReadableByteStreamController and a ${viewType} view`); } } - -function getConstructorAndRunTests() { - let ReadableStreamBYOBRequest; - const rs = new ReadableStream({ - pull(controller) { - const byobRequest = controller.byobRequest; - ReadableStreamBYOBRequest = byobRequest.constructor; - byobRequest.respond(4); - }, - type: 'bytes' - }); - rs.getReader({ mode: 'byob' }).read(new Uint8Array(8)).then(() => { - runTests(ReadableStreamBYOBRequest); - done(); - }); -} - -// We can only get at the ReadableStreamBYOBRequest constructor asynchronously, so we need to make the test harness wait -// for us to explicitly tell it all our tests have run. -setup({ explicit_done: true }); - -getConstructorAndRunTests(); diff --git a/streams/readable-byte-streams/constructor.any.js b/streams/readable-byte-streams/constructor.any.js deleted file mode 100644 index eced9f9525a495..00000000000000 --- a/streams/readable-byte-streams/constructor.any.js +++ /dev/null @@ -1,48 +0,0 @@ -// META: global=window,worker,jsshell -// META: script=../resources/constructor-ordering.js -'use strict'; - -const operations = [ - op('get', 'size'), - op('get', 'highWaterMark'), - op('get', 'type'), - op('validate', 'type'), - op('validate', 'size'), - op('tonumber', 'highWaterMark'), - op('validate', 'highWaterMark'), - op('get', 'pull'), - op('validate', 'pull'), - op('get', 'cancel'), - op('validate', 'cancel'), - op('get', 'autoAllocateChunkSize'), - op('tonumber', 'autoAllocateChunkSize'), - op('validate', 'autoAllocateChunkSize'), - op('get', 'start'), - op('validate', 'start') -]; - -for (const failureOp of operations) { - test(() => { - const record = new OpRecorder(failureOp); - const underlyingSource = createRecordingObjectWithProperties(record, ['start', 'pull', 'cancel']); - - // The valid value for "type" is "bytes", so set it separately. - defineCheckedProperty(record, underlyingSource, 'type', () => record.check('type') ? 'invalid' : 'bytes'); - - // autoAllocateChunkSize is a special case because it has a tonumber step. - defineCheckedProperty(record, underlyingSource, 'autoAllocateChunkSize', - () => createRecordingNumberObject(record, 'autoAllocateChunkSize')); - - const strategy = createRecordingStrategy(record); - - try { - new ReadableStream(underlyingSource, strategy); - assert_unreached('constructor should throw'); - } catch (e) { - assert_equals(typeof e, 'object', 'e should be an object'); - } - - assert_equals(record.actual(), expectedAsString(operations, failureOp), - 'operations should be performed in the right order'); - }, `ReadableStream constructor should stop after ${failureOp} fails`); -} diff --git a/streams/readable-byte-streams/general.any.js b/streams/readable-byte-streams/general.any.js index 70b795358f3a1c..e384dbc5c7944b 100644 --- a/streams/readable-byte-streams/general.any.js +++ b/streams/readable-byte-streams/general.any.js @@ -307,8 +307,8 @@ promise_test(() => { const byobRequest = controller.byobRequest; const view = byobRequest.view; byobRequests[pullCount] = { - defined: byobRequest !== undefined, - viewDefined: view !== undefined, + nonNull: byobRequest !== null, + viewNonNull: view !== null, viewInfo: extractViewInfo(view) }; if (pullCount === 0) { @@ -337,8 +337,8 @@ promise_test(() => { return Promise.resolve().then(() => { assert_equals(pullCount, 1, 'pull() must have been invoked once'); const byobRequest = byobRequests[0]; - assert_true(byobRequest.defined, 'first byobRequest must not be undefined'); - assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined'); + assert_true(byobRequest.nonNull, 'first byobRequest must not be null'); + assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null'); const viewInfo = byobRequest.viewInfo; assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array'); assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16'); @@ -356,8 +356,8 @@ promise_test(() => { assert_equals(value.byteLength, 1, 'first value.byteLength should be 1'); assert_equals(value[0], 0x01, 'first value[0] should be 0x01'); const byobRequest = byobRequests[1]; - assert_true(byobRequest.defined, 'second byobRequest must not be undefined'); - assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined'); + assert_true(byobRequest.nonNull, 'second byobRequest must not be null'); + assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null'); const viewInfo = byobRequest.viewInfo; assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array'); assert_equals(viewInfo.bufferByteLength, 16, 'second view.buffer.byteLength should be 16'); @@ -391,8 +391,8 @@ promise_test(() => { const byobRequest = controller.byobRequest; const view = byobRequest.view; byobRequests[pullCount] = { - defined: byobRequest !== undefined, - viewDefined: view !== undefined, + nonNull: byobRequest !== null, + viewNonNull: view !== null, viewInfo: extractViewInfo(view) }; if (pullCount === 0) { @@ -422,8 +422,8 @@ promise_test(() => { assert_equals(value.byteLength, 1, 'first value.byteLength should be 1'); assert_equals(value[0], 0x01, 'first value[0] should be 0x01'); const byobRequest = byobRequests[0]; - assert_true(byobRequest.defined, 'first byobRequest must not be undefined'); - assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined'); + assert_true(byobRequest.nonNull, 'first byobRequest must not be null'); + assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null'); const viewInfo = byobRequest.viewInfo; assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array'); assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16'); @@ -443,8 +443,8 @@ promise_test(() => { assert_equals(value[0], 0x02, 'second value[0] should be 0x02'); assert_equals(value[1], 0x03, 'second value[1] should be 0x03'); const byobRequest = byobRequests[1]; - assert_true(byobRequest.defined, 'second byobRequest must not be undefined'); - assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined'); + assert_true(byobRequest.nonNull, 'second byobRequest must not be null'); + assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null'); const viewInfo = byobRequest.viewInfo; assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array'); assert_equals(viewInfo.bufferByteLength, 32, 'second view.buffer.byteLength should be 32'); @@ -695,7 +695,7 @@ promise_test(() => { return reader.read().then(result => { assert_equals(result.done, false, 'done'); assert_equals(result.value.byteLength, 16, 'byteLength'); - assert_equals(byobRequest, undefined, 'byobRequest must be undefined'); + assert_equals(byobRequest, null, 'byobRequest must be null'); }); }, 'ReadableStream with byte source: Respond to pull() by enqueue()'); @@ -745,7 +745,7 @@ promise_test(() => { assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength'); assert_equals(result[2].done, false, 'result[2].done'); assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength'); - assert_equals(byobRequest, undefined, 'byobRequest should be undefined'); + assert_equals(byobRequest, null, 'byobRequest should be null'); assert_equals(desiredSizes[0], 0, 'desiredSize on pull should be 0'); assert_equals(desiredSizes[1], 0, 'desiredSize after 1st enqueue() should be 0'); assert_equals(desiredSizes[2], 0, 'desiredSize after 2nd enqueue() should be 0'); @@ -794,7 +794,7 @@ promise_test(() => { assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength'); assert_equals(result[2].done, false, 'result[2].done'); assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength'); - assert_equals(byobRequest, undefined, 'byobRequest should be undefined'); + assert_equals(byobRequest, null, 'byobRequest should be null'); assert_equals(desiredSizes[0], 256, 'desiredSize on pull should be 256'); assert_equals(desiredSizes[1], 256, 'desiredSize after 1st enqueue() should be 256'); assert_equals(desiredSizes[2], 256, 'desiredSize after 2nd enqueue() should be 256'); @@ -813,13 +813,13 @@ promise_test(() => { controller = c; }, pull() { - byobRequestDefined.push(controller.byobRequest !== undefined); + byobRequestDefined.push(controller.byobRequest !== null); const view = controller.byobRequest.view; view[0] = 0x01; controller.byobRequest.respond(1); - byobRequestDefined.push(controller.byobRequest !== undefined); + byobRequestDefined.push(controller.byobRequest !== null); ++pullCount; }, @@ -833,8 +833,8 @@ promise_test(() => { assert_equals(result.value.byteLength, 1, 'result.value.byteLength'); assert_equals(result.value[0], 0x01, 'result.value[0]'); assert_equals(pullCount, 1, 'pull() should be called only once'); - assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()'); - assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()'); + assert_true(byobRequestDefined[0], 'byobRequest must not be null before respond()'); + assert_false(byobRequestDefined[1], 'byobRequest must be null after respond()'); }); }, 'ReadableStream with byte source: read(view), then respond()'); @@ -849,7 +849,7 @@ promise_test(() => { controller = c; }, pull() { - byobRequestDefined.push(controller.byobRequest !== undefined); + byobRequestDefined.push(controller.byobRequest !== null); // Emulate ArrayBuffer transfer by just creating a new ArrayBuffer and pass it. By checking the result of // read(view), we test that the respond()'s buffer argument is working correctly. @@ -860,7 +860,7 @@ promise_test(() => { transferredView[0] = 0x01; controller.byobRequest.respondWithNewView(transferredView); - byobRequestDefined.push(controller.byobRequest !== undefined); + byobRequestDefined.push(controller.byobRequest !== null); ++pullCount; }, @@ -874,8 +874,8 @@ promise_test(() => { assert_equals(result.value.byteLength, 1, 'result.value.byteLength'); assert_equals(result.value[0], 0x01, 'result.value[0]'); assert_equals(pullCount, 1, 'pull() should be called only once'); - assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()'); - assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()'); + assert_true(byobRequestDefined[0], 'byobRequest must not be null before respond()'); + assert_false(byobRequestDefined[1], 'byobRequest must be null after respond()'); }); }, 'ReadableStream with byte source: read(view), then respond() with a transferred ArrayBuffer'); @@ -889,7 +889,7 @@ promise_test(() => { controller = c; }, pull() { - byobRequestWasDefined = controller.byobRequest !== undefined; + byobRequestWasDefined = controller.byobRequest !== null; try { controller.byobRequest.respond(2); @@ -905,7 +905,7 @@ promise_test(() => { const reader = stream.getReader({ mode: 'byob' }); return reader.read(new Uint8Array(1)).then(() => { - assert_true(byobRequestWasDefined, 'byobRequest should be defined'); + assert_true(byobRequestWasDefined, 'byobRequest should be non-null'); assert_not_equals(incorrectRespondException, undefined, 'respond() must throw'); assert_equals(incorrectRespondException.name, 'RangeError', 'respond() must throw a RangeError'); }); @@ -954,7 +954,7 @@ promise_test(() => { return reader.read(new Uint8Array(1)); }).then(result => { assert_equals(pullCount, 1); - assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'); + assert_not_equals(byobRequest, null, 'byobRequest must not be null'); assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array'); assert_equals(viewInfo.bufferByteLength, 4, 'view.buffer.byteLength should be 4'); assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0'); @@ -1129,7 +1129,7 @@ promise_test(() => { }); assert_equals(pullCount, 1, '1 pull() should have been made in response to partial fill by enqueue()'); - assert_not_equals(byobRequest, undefined, 'byobRequest should not be undefined'); + assert_not_equals(byobRequest, null, 'byobRequest should not be null'); assert_equals(viewInfos[0].byteLength, 2, 'byteLength before enqueue() shouild be 2'); assert_equals(viewInfos[1].byteLength, 1, 'byteLength after enqueue() should be 1'); @@ -1146,7 +1146,7 @@ promise_test(() => { promise_test(() => { let controller; - let byobRequest; + let pullCalled = false; const stream = new ReadableStream({ start(c) { @@ -1157,7 +1157,7 @@ promise_test(() => { controller = c; }, pull() { - byobRequest = controller.byobRequest; + pullCalled = true; }, type: 'bytes' }); @@ -1169,7 +1169,7 @@ promise_test(() => { return reader.read(new Uint8Array(buffer, 8, 8)).then(result => { assert_equals(result.done, false); - assert_equals(byobRequest, undefined, 'byobRequest must be undefined'); + assert_false(pullCalled, 'pull() must not have been called'); const view = result.value; assert_equals(view.constructor, Uint8Array); @@ -1183,7 +1183,7 @@ promise_test(() => { promise_test(() => { let controller; - let byobRequest; + let pullCalled = false; const stream = new ReadableStream({ start(c) { @@ -1200,7 +1200,7 @@ promise_test(() => { controller = c; }, pull() { - byobRequest = controller.byobRequest; + pullCalled = true; }, type: 'bytes' }); @@ -1210,7 +1210,7 @@ promise_test(() => { return reader.read(new Uint8Array(24)).then(result => { assert_equals(result.done, false, 'done'); - assert_equals(byobRequest, undefined, 'byobRequest must be undefined'); + assert_false(pullCalled, 'pull() must not have been called'); const view = result.value; assert_equals(view.byteOffset, 0, 'byteOffset'); @@ -1221,7 +1221,7 @@ promise_test(() => { }, 'ReadableStream with byte source: Multiple enqueue(), getReader(), then read(view)'); promise_test(() => { - let byobRequest; + let pullCalled = false; const stream = new ReadableStream({ start(c) { @@ -1229,8 +1229,8 @@ promise_test(() => { view[15] = 0x01; c.enqueue(view); }, - pull(controller) { - byobRequest = controller.byobRequest; + pull() { + pullCalled = true; }, type: 'bytes' }); @@ -1240,7 +1240,7 @@ promise_test(() => { return reader.read(new Uint8Array(24)).then(result => { assert_equals(result.done, false); - assert_equals(byobRequest, undefined, 'byobRequest must be undefined'); + assert_false(pullCalled, 'pull() must not have been called'); const view = result.value; assert_equals(view.byteOffset, 0); @@ -1250,7 +1250,7 @@ promise_test(() => { }, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a bigger view'); promise_test(() => { - let byobRequest; + let pullCalled = false; const stream = new ReadableStream({ start(c) { @@ -1259,8 +1259,8 @@ promise_test(() => { view[15] = 0x02; c.enqueue(view); }, - pull(controller) { - byobRequest = controller.byobRequest; + pull() { + pullCalled = true; }, type: 'bytes' }); @@ -1279,14 +1279,14 @@ promise_test(() => { }).then(result => { assert_equals(result.done, false, 'done'); - assert_equals(byobRequest, undefined, 'byobRequest must be undefined'); + assert_false(pullCalled, 'pull() must not have been called'); const view = result.value; assert_equals(view.byteOffset, 0); assert_equals(view.byteLength, 8); assert_equals(view[7], 0x02); }); -}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a smaller views'); +}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with smaller views'); promise_test(() => { let controller; @@ -1301,7 +1301,7 @@ promise_test(() => { controller = c; }, pull() { - if (controller.byobRequest === undefined) { + if (controller.byobRequest === null) { return; } @@ -1393,7 +1393,7 @@ promise_test(() => { assert_equals(view.byteLength, 2, 'byteLength'); assert_equals(view[0], 0x0302, 'Contents are set'); - assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'); + assert_not_equals(byobRequest, null, 'byobRequest must not be null'); assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array'); assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2'); assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1'); @@ -1530,7 +1530,7 @@ promise_test(() => { assert_equals(view.byteOffset, 0); assert_equals(view.byteLength, 0); - assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'); + assert_not_equals(byobRequest, null, 'byobRequest must not be null'); assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array'); assert_equals(viewInfo.bufferByteLength, 16, 'view.buffer.byteLength should be 16'); assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0'); @@ -1549,7 +1549,7 @@ promise_test(() => { controller = c; }, pull() { - if (controller.byobRequest === undefined) { + if (controller.byobRequest === null) { return; } @@ -1638,7 +1638,7 @@ promise_test(() => { assert_equals(view.byteOffset, 0); assert_equals(view.byteLength, 2); - assert_equals(byobRequest, undefined, 'byobRequest must be undefined'); + assert_equals(byobRequest, null, 'byobRequest must be null'); }); assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled'); @@ -1760,10 +1760,10 @@ promise_test(t => { }, 'ReadableStream with byte source: Multiple read(view) and multiple enqueue()'); promise_test(t => { - let byobRequest; + let pullCalled = false; const stream = new ReadableStream({ pull(controller) { - byobRequest = controller.byobRequest; + pullCalled = true; }, type: 'bytes' }); @@ -1771,24 +1771,9 @@ promise_test(t => { const reader = stream.getReader({ mode: 'byob' }); return promise_rejects_js(t, TypeError, reader.read(), 'read() must fail') - .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined')); + .then(() => assert_false(pullCalled, 'pull() must not have been called')); }, 'ReadableStream with byte source: read(view) with passing undefined as view must fail'); -promise_test(t => { - let byobRequest; - const stream = new ReadableStream({ - pull(controller) { - byobRequest = controller.byobRequest; - }, - type: 'bytes' - }); - - const reader = stream.getReader({ mode: 'byob' }); - - return promise_rejects_js(t, TypeError, reader.read(new Uint8Array(0)), 'read(view) must fail') - .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined')); -}, 'ReadableStream with byte source: read(view) with zero-length view must fail'); - promise_test(t => { const stream = new ReadableStream({ type: 'bytes' @@ -1898,7 +1883,7 @@ promise_test(t => { const promise = promise_rejects_exactly(t, testError, reader.read(), 'read() must fail'); return promise_rejects_exactly(t, testError, promise.then(() => reader.closed)) - .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined')); + .then(() => assert_equals(byobRequest, null, 'byobRequest must be null')); }, 'ReadableStream with byte source: Throwing in pull function must error the stream'); promise_test(t => { @@ -1917,7 +1902,7 @@ promise_test(t => { return promise_rejects_exactly(t, error1, reader.read(), 'read() must fail') .then(() => promise_rejects_exactly(t, error1, reader.closed, 'closed must fail')) - .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined')); + .then(() => assert_equals(byobRequest, null, 'byobRequest must be null')); }, 'ReadableStream with byte source: Throwing in pull in response to read() must be ignored if the stream is ' + 'errored in it'); @@ -1938,7 +1923,7 @@ promise_test(t => { return promise_rejects_exactly(t, testError, reader.read(new Uint8Array(1)), 'read(view) must fail') .then(() => promise_rejects_exactly(t, testError, reader.closed, 'reader.closed must reject')) - .then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined')); + .then(() => assert_not_equals(byobRequest, null, 'byobRequest must not be null')); }, 'ReadableStream with byte source: Throwing in pull in response to read(view) function must error the stream'); promise_test(t => { @@ -1957,7 +1942,7 @@ promise_test(t => { return promise_rejects_exactly(t, error1, reader.read(new Uint8Array(1)), 'read(view) must fail') .then(() => promise_rejects_exactly(t, error1, reader.closed, 'closed must fail')) - .then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined')); + .then(() => assert_not_equals(byobRequest, null, 'byobRequest must not be null')); }, 'ReadableStream with byte source: Throwing in pull in response to read(view) must be ignored if the stream is ' + 'errored in it'); @@ -2101,9 +2086,6 @@ test(() => { } }), 'constructor should throw for size function'); - assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, { size: null }), - 'constructor should throw for size defined'); - assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, new CountQueuingStrategy({ highWaterMark: 1 })), 'constructor should throw when strategy is CountQueuingStrategy'); diff --git a/streams/readable-byte-streams/properties.any.js b/streams/readable-byte-streams/properties.any.js deleted file mode 100644 index 24e1edc93bff88..00000000000000 --- a/streams/readable-byte-streams/properties.any.js +++ /dev/null @@ -1,142 +0,0 @@ -// META: global=window,worker,jsshell -// META: script=../resources/rs-utils.js -'use strict'; - -let ReadableStreamBYOBReader; - -test(() => { - - // It's not exposed globally, but we test a few of its properties here. - ReadableStreamBYOBReader = (new ReadableStream({ type: 'bytes' })) - .getReader({ mode: 'byob' }).constructor; - -}, 'Can get the ReadableStreamBYOBReader constructor indirectly'); - -test(() => { - - assert_throws_js(TypeError, () => new ReadableStreamBYOBReader('potato')); - assert_throws_js(TypeError, () => new ReadableStreamBYOBReader({})); - assert_throws_js(TypeError, () => new ReadableStreamBYOBReader()); - -}, 'ReadableStreamBYOBReader constructor should get a ReadableStream object as argument'); - -test(() => { - - const methods = ['cancel', 'constructor', 'read', 'releaseLock']; - const properties = methods.concat(['closed']).sort(); - - const rsReader = new ReadableStreamBYOBReader(new ReadableStream({ type: 'bytes' })); - const proto = Object.getPrototypeOf(rsReader); - - assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties); - - for (const m of methods) { - const propDesc = Object.getOwnPropertyDescriptor(proto, m); - assert_equals(propDesc.enumerable, false, 'method should be non-enumerable'); - assert_equals(propDesc.configurable, true, 'method should be configurable'); - assert_equals(propDesc.writable, true, 'method should be writable'); - assert_equals(typeof rsReader[m], 'function', 'should have be a method'); - const expectedName = m === 'constructor' ? 'ReadableStreamBYOBReader' : m; - assert_equals(rsReader[m].name, expectedName, 'method should have the correct name'); - } - - const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed'); - assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable'); - assert_equals(closedPropDesc.configurable, true, 'closed should be configurable'); - assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter'); - assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter'); - - assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter'); - assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property'); - assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable'); - assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter'); - assert_equals(rsReader.read.length, 1, 'read has 1 parameter'); - assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters'); - -}, 'ReadableStreamBYOBReader instances should have the correct list of properties'); - -promise_test(t => { - - const rs = new ReadableStream({ - pull(controller) { - return t.step(() => { - const byobRequest = controller.byobRequest; - - const methods = ['constructor', 'respond', 'respondWithNewView']; - const properties = methods.concat(['view']).sort(); - - const proto = Object.getPrototypeOf(byobRequest); - - assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties); - - for (const m of methods) { - const propDesc = Object.getOwnPropertyDescriptor(proto, m); - assert_equals(propDesc.enumerable, false, 'method should be non-enumerable'); - assert_equals(propDesc.configurable, true, 'method should be configurable'); - assert_equals(propDesc.writable, true, 'method should be writable'); - assert_equals(typeof byobRequest[m], 'function', 'should have a method'); - const expectedName = m === 'constructor' ? 'ReadableStreamBYOBRequest' : m; - assert_equals(byobRequest[m].name, expectedName, 'method should have the correct name'); - } - - const viewPropDesc = Object.getOwnPropertyDescriptor(proto, 'view'); - assert_equals(viewPropDesc.enumerable, false, 'view should be non-enumerable'); - assert_equals(viewPropDesc.configurable, true, 'view should be configurable'); - assert_not_equals(viewPropDesc.get, undefined, 'view should have a getter'); - assert_equals(viewPropDesc.set, undefined, 'view should not have a setter'); - assert_not_equals(byobRequest.view, undefined, 'has a non-undefined view property'); - assert_equals(byobRequest.constructor.length, 0, 'constructor has 0 parameters'); - assert_equals(byobRequest.respond.length, 1, 'respond has 1 parameter'); - assert_equals(byobRequest.respondWithNewView.length, 1, 'releaseLock has 1 parameter'); - - byobRequest.respond(1); - - }); - }, - type: 'bytes' }); - const reader = rs.getReader({ mode: 'byob' }); - return reader.read(new Uint8Array(1)); - -}, 'ReadableStreamBYOBRequest instances should have the correct list of properties'); - -test(() => { - - const methods = ['close', 'constructor', 'enqueue', 'error']; - const accessors = ['byobRequest', 'desiredSize']; - const properties = methods.concat(accessors).sort(); - - let controller; - new ReadableStream({ - start(c) { - controller = c; - }, - type: 'bytes' - }); - const proto = Object.getPrototypeOf(controller); - - assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties); - - for (const m of methods) { - const propDesc = Object.getOwnPropertyDescriptor(proto, m); - assert_equals(propDesc.enumerable, false, 'method should be non-enumerable'); - assert_equals(propDesc.configurable, true, 'method should be configurable'); - assert_equals(propDesc.writable, true, 'method should be writable'); - assert_equals(typeof controller[m], 'function', 'should have be a method'); - const expectedName = m === 'constructor' ? 'ReadableByteStreamController' : m; - assert_equals(controller[m].name, expectedName, 'method should have the correct name'); - } - - for (const a of accessors) { - const propDesc = Object.getOwnPropertyDescriptor(proto, a); - assert_equals(propDesc.enumerable, false, `${a} should be non-enumerable`); - assert_equals(propDesc.configurable, true, `${a} should be configurable`); - assert_not_equals(propDesc.get, undefined, `${a} should have a getter`); - assert_equals(propDesc.set, undefined, `${a} should not have a setter`); - } - - assert_equals(controller.close.length, 0, 'cancel has no parameters'); - assert_equals(controller.constructor.length, 0, 'constructor has no parameters'); - assert_equals(controller.enqueue.length, 1, 'enqueue has 1 parameter'); - assert_equals(controller.error.length, 1, 'releaseLock has 1 parameter'); - -}, 'ReadableByteStreamController instances should have the correct list of properties'); diff --git a/streams/readable-streams/async-iterator.any.js b/streams/readable-streams/async-iterator.any.js index d867eb00cfdd90..a1acaeb6b1f864 100644 --- a/streams/readable-streams/async-iterator.any.js +++ b/streams/readable-streams/async-iterator.any.js @@ -4,13 +4,24 @@ // META: script=../resources/recording-streams.js 'use strict'; +const error1 = new Error('error1'); + +function assert_iter_result(iterResult, value, done, message) { + const prefix = message === undefined ? '' : `${message} `; + assert_equals(typeof iterResult, 'object', `${prefix}type is object`); + assert_equals(Object.getPrototypeOf(iterResult), Object.prototype, `${prefix}[[Prototype]]`); + assert_array_equals(Object.getOwnPropertyNames(iterResult).sort(), ['done', 'value'], `${prefix}property names`); + assert_equals(iterResult.value, value, `${prefix}value`); + assert_equals(iterResult.done, done, `${prefix}done`); +} + test(() => { - assert_equals(ReadableStream.prototype[Symbol.asyncIterator], ReadableStream.prototype.getIterator); -}, '@@asyncIterator() method is === to getIterator() method'); + assert_equals(ReadableStream.prototype[Symbol.asyncIterator], ReadableStream.prototype.values); +}, '@@asyncIterator() method is === to values() method'); test(() => { const s = new ReadableStream(); - const it = s.getIterator(); + const it = s.values(); const proto = Object.getPrototypeOf(it); const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype); @@ -21,7 +32,7 @@ test(() => { for (const m of methods) { const propDesc = Object.getOwnPropertyDescriptor(proto, m); - assert_false(propDesc.enumerable, 'method should be non-enumerable'); + assert_true(propDesc.enumerable, 'method should be enumerable'); assert_true(propDesc.configurable, 'method should be configurable'); assert_true(propDesc.writable, 'method should be writable'); assert_equals(typeof it[m], 'function', 'method should be a function'); @@ -40,7 +51,7 @@ promise_test(async () => { c.enqueue(2); c.enqueue(3); c.close(); - }, + } }); const chunks = []; @@ -59,7 +70,7 @@ promise_test(async () => { c.close(); } i += 1; - }, + } }); const chunks = []; @@ -69,6 +80,42 @@ promise_test(async () => { assert_array_equals(chunks, [1, 2, 3]); }, 'Async-iterating a pull source'); +promise_test(async () => { + const s = new ReadableStream({ + start(c) { + c.enqueue(undefined); + c.enqueue(undefined); + c.enqueue(undefined); + c.close(); + } + }); + + const chunks = []; + for await (const chunk of s) { + chunks.push(chunk); + } + assert_array_equals(chunks, [undefined, undefined, undefined]); +}, 'Async-iterating a push source with undefined values'); + +promise_test(async () => { + let i = 1; + const s = new ReadableStream({ + pull(c) { + c.enqueue(undefined); + if (i >= 3) { + c.close(); + } + i += 1; + } + }); + + const chunks = []; + for await (const chunk of s) { + chunks.push(chunk); + } + assert_array_equals(chunks, [undefined, undefined, undefined]); +}, 'Async-iterating a pull source with undefined values'); + promise_test(async () => { let i = 1; const s = recordingReadableStream({ @@ -81,27 +128,23 @@ promise_test(async () => { }, }, new CountQueuingStrategy({ highWaterMark: 0 })); - const it = s.getIterator(); + const it = s.values(); assert_array_equals(s.events, []); const read1 = await it.next(); - assert_equals(read1.done, false); - assert_equals(read1.value, 1); + assert_iter_result(read1, 1, false); assert_array_equals(s.events, ['pull']); const read2 = await it.next(); - assert_equals(read2.done, false); - assert_equals(read2.value, 2); + assert_iter_result(read2, 2, false); assert_array_equals(s.events, ['pull', 'pull']); const read3 = await it.next(); - assert_equals(read3.done, false); - assert_equals(read3.value, 3); + assert_iter_result(read3, 3, false); assert_array_equals(s.events, ['pull', 'pull', 'pull']); const read4 = await it.next(); - assert_equals(read4.done, true); - assert_equals(read4.value, undefined); + assert_iter_result(read4, undefined, true); assert_array_equals(s.events, ['pull', 'pull', 'pull']); }, 'Async-iterating a pull source manually'); @@ -160,8 +203,7 @@ promise_test(async () => { const reader = s.getReader(); const readResult = await reader.read(); - assert_equals(readResult.done, false); - assert_equals(readResult.value, 1); + assert_iter_result(readResult, 1, false); reader.releaseLock(); const chunks = []; @@ -182,7 +224,7 @@ for (const type of ['throw', 'break', 'return']) { // use a separate function for the loop body so return does not stop the test const loop = async () => { - for await (const c of s.getIterator({ preventCancel })) { + for await (const c of s.values({ preventCancel })) { if (type === 'throw') { throw new Error(); } else if (type === 'break') { @@ -214,7 +256,7 @@ for (const preventCancel of [false, true]) { } }); - const it = s.getIterator({ preventCancel }); + const it = s.values({ preventCancel }); await it.return(); if (preventCancel) { @@ -226,33 +268,249 @@ for (const preventCancel of [false, true]) { } promise_test(async t => { - const s = new ReadableStream(); + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + if (timesPulled === 0) { + c.enqueue(0); + ++timesPulled; + } else { + c.error(error1); + } + } + }); + const it = s[Symbol.asyncIterator](); - await it.return(); - return promise_rejects_js(t, TypeError, it.return(), 'return should reject'); -}, 'Calling return() twice rejects'); + + const iterResult1 = await it.next(); + assert_iter_result(iterResult1, 0, false, '1st next()'); + + await promise_rejects_exactly(t, error1, it.next(), '2nd next()'); +}, 'next() rejects if the stream errors'); promise_test(async () => { + let timesPulled = 0; const s = new ReadableStream({ - start(c) { - c.enqueue(0); - c.close(); - }, + pull(c) { + if (timesPulled === 0) { + c.enqueue(0); + ++timesPulled; + } else { + c.error(error1); + } + } }); + const it = s[Symbol.asyncIterator](); - const next = await it.next(); - assert_equals(Object.getPrototypeOf(next), Object.prototype); - assert_array_equals(Object.getOwnPropertyNames(next).sort(), ['done', 'value']); -}, 'next()\'s fulfillment value has the right shape'); + + const iterResult = await it.return('return value'); + assert_iter_result(iterResult, 'return value', true); +}, 'return() does not rejects if the stream has not errored yet'); promise_test(async t => { - const s = recordingReadableStream(); + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + // Do not error in start() because doing so would prevent acquiring a reader/async iterator. + c.error(error1); + } + }); + const it = s[Symbol.asyncIterator](); - it.next(); - await promise_rejects_js(t, TypeError, it.return(), 'return() should reject'); - assert_array_equals(s.events, ['pull']); -}, 'calling return() while there are pending reads rejects'); + await flushAsyncEvents(); + await promise_rejects_exactly(t, error1, it.return('return value')); +}, 'return() rejects if the stream has errored'); + +promise_test(async t => { + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + if (timesPulled === 0) { + c.enqueue(0); + ++timesPulled; + } else { + c.error(error1); + } + } + }); + + const it = s[Symbol.asyncIterator](); + + const iterResult1 = await it.next(); + assert_iter_result(iterResult1, 0, false, '1st next()'); + + await promise_rejects_exactly(t, error1, it.next(), '2nd next()'); + + const iterResult3 = await it.next(); + assert_iter_result(iterResult3, undefined, true, '3rd next()'); +}, 'next() that succeeds; next() that reports an error; next()'); + +promise_test(async () => { + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + if (timesPulled === 0) { + c.enqueue(0); + ++timesPulled; + } else { + c.error(error1); + } + } + }); + + const it = s[Symbol.asyncIterator](); + + const iterResults = await Promise.allSettled([it.next(), it.next(), it.next()]); + + assert_equals(iterResults[0].status, 'fulfilled', '1st next() promise status'); + assert_iter_result(iterResults[0].value, 0, false, '1st next()'); + + assert_equals(iterResults[1].status, 'rejected', '2nd next() promise status'); + assert_equals(iterResults[1].reason, error1, '2nd next() rejection reason'); + + assert_equals(iterResults[2].status, 'fulfilled', '3rd next() promise status'); + assert_iter_result(iterResults[2].value, undefined, true, '3rd next()'); +}, 'next() that succeeds; next() that reports an error(); next() [no awaiting]'); + +promise_test(async t => { + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + if (timesPulled === 0) { + c.enqueue(0); + ++timesPulled; + } else { + c.error(error1); + } + } + }); + + const it = s[Symbol.asyncIterator](); + + const iterResult1 = await it.next(); + assert_iter_result(iterResult1, 0, false, '1st next()'); + + await promise_rejects_exactly(t, error1, it.next(), '2nd next()'); + + const iterResult3 = await it.return('return value'); + assert_iter_result(iterResult3, 'return value', true, 'return()'); +}, 'next() that succeeds; next() that reports an error(); return()'); + +promise_test(async () => { + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + if (timesPulled === 0) { + c.enqueue(0); + ++timesPulled; + } else { + c.error(error1); + } + } + }); + + const it = s[Symbol.asyncIterator](); + + const iterResults = await Promise.allSettled([it.next(), it.next(), it.return('return value')]); + + assert_equals(iterResults[0].status, 'fulfilled', '1st next() promise status'); + assert_iter_result(iterResults[0].value, 0, false, '1st next()'); + + assert_equals(iterResults[1].status, 'rejected', '2nd next() promise status'); + assert_equals(iterResults[1].reason, error1, '2nd next() rejection reason'); + + assert_equals(iterResults[2].status, 'fulfilled', 'return() promise status'); + assert_iter_result(iterResults[2].value, 'return value', true, 'return()'); +}, 'next() that succeeds; next() that reports an error(); return() [no awaiting]'); + +promise_test(async () => { + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + c.enqueue(timesPulled); + ++timesPulled; + } + }); + const it = s[Symbol.asyncIterator](); + + const iterResult1 = await it.next(); + assert_iter_result(iterResult1, 0, false, 'next()'); + + const iterResult2 = await it.return('return value'); + assert_iter_result(iterResult2, 'return value', true, 'return()'); + + assert_equals(timesPulled, 2); +}, 'next() that succeeds; return()'); + +promise_test(async () => { + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + c.enqueue(timesPulled); + ++timesPulled; + } + }); + const it = s[Symbol.asyncIterator](); + + const iterResults = await Promise.allSettled([it.next(), it.return('return value')]); + + assert_equals(iterResults[0].status, 'fulfilled', 'next() promise status'); + assert_iter_result(iterResults[0].value, 0, false, 'next()'); + + assert_equals(iterResults[1].status, 'fulfilled', 'return() promise status'); + assert_iter_result(iterResults[1].value, 'return value', true, 'return()'); + + assert_equals(timesPulled, 2); +}, 'next() that succeeds; return() [no awaiting]'); + +promise_test(async () => { + const rs = new ReadableStream(); + const it = rs.values(); + + const iterResult1 = await it.return('return value'); + assert_iter_result(iterResult1, 'return value', true, 'return()'); + + const iterResult2 = await it.next(); + assert_iter_result(iterResult2, undefined, true, 'next()'); +}, 'return(); next()'); + +promise_test(async () => { + const rs = new ReadableStream(); + const it = rs.values(); + + const iterResults = await Promise.allSettled([it.return('return value'), it.next()]); + + assert_equals(iterResults[0].status, 'fulfilled', 'return() promise status'); + assert_iter_result(iterResults[0].value, 'return value', true, 'return()'); + + assert_equals(iterResults[1].status, 'fulfilled', 'next() promise status'); + assert_iter_result(iterResults[1].value, undefined, true, 'next()'); +}, 'return(); next() [no awaiting]'); + +promise_test(async () => { + const rs = new ReadableStream(); + const it = rs.values(); + + const iterResult1 = await it.return('return value 1'); + assert_iter_result(iterResult1, 'return value 1', true, '1st return()'); + + const iterResult2 = await it.return('return value 2'); + assert_iter_result(iterResult2, 'return value 2', true, '1st return()'); +}, 'return(); return()'); + +promise_test(async () => { + const rs = new ReadableStream(); + const it = rs.values(); + + const iterResults = await Promise.allSettled([it.return('return value 1'), it.return('return value 2')]); + + assert_equals(iterResults[0].status, 'fulfilled', '1st return() promise status'); + assert_iter_result(iterResults[0].value, 'return value 1', true, '1st return()'); + + assert_equals(iterResults[1].status, 'fulfilled', '2nd return() promise status'); + assert_iter_result(iterResults[1].value, 'return value 2', true, '1st return()'); +}, 'return(); return() [no awaiting]'); test(() => { const s = new ReadableStream({ @@ -261,9 +519,9 @@ test(() => { c.close(); }, }); - const it = s.getIterator(); - assert_throws_js(TypeError, () => s.getIterator(), 'getIterator() should throw'); -}, 'getIterator() throws if there\'s already a lock'); + s.values(); + assert_throws_js(TypeError, () => s.values(), 'values() should throw'); +}, 'values() throws if there\'s already a lock'); promise_test(async () => { const s = new ReadableStream({ @@ -272,7 +530,7 @@ promise_test(async () => { c.enqueue(2); c.enqueue(3); c.close(); - }, + } }); const chunks = []; @@ -285,6 +543,34 @@ promise_test(async () => { await reader.closed; }, 'Acquiring a reader after exhaustively async-iterating a stream'); +promise_test(async t => { + let timesPulled = 0; + const s = new ReadableStream({ + pull(c) { + if (timesPulled === 0) { + c.enqueue(0); + ++timesPulled; + } else { + c.error(error1); + } + } + }); + + const it = s[Symbol.asyncIterator]({ preventCancel: true }); + + const iterResult1 = await it.next(); + assert_iter_result(iterResult1, 0, false, '1st next()'); + + await promise_rejects_exactly(t, error1, it.next(), '2nd next()'); + + const iterResult2 = await it.return('return value'); + assert_iter_result(iterResult2, 'return value', true, 'return()'); + + // i.e. it should not reject with a generic "this stream is locked" TypeError. + const reader = s.getReader(); + await promise_rejects_exactly(t, error1, reader.closed, 'closed on the new reader should reject with the error'); +}, 'Acquiring a reader after return()ing from a stream that errors'); + promise_test(async () => { const s = new ReadableStream({ start(c) { @@ -321,7 +607,7 @@ promise_test(async () => { // read the first two chunks, then release lock const chunks = []; - for await (const chunk of s.getIterator({preventCancel: true})) { + for await (const chunk of s.values({preventCancel: true})) { chunks.push(chunk); if (chunk >= 2) { break; @@ -331,22 +617,14 @@ promise_test(async () => { const reader = s.getReader(); const readResult = await reader.read(); - assert_equals(readResult.done, false, 'should not be closed yet'); - assert_equals(readResult.value, 3, 'should read remaining chunk'); + assert_iter_result(readResult, 3, false); await reader.closed; }, 'Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true'); -promise_test(async t => { - const rs = new ReadableStream(); - const it = rs.getIterator(); - await it.return(); - return promise_rejects_js(t, TypeError, it.next(), 'next() should reject'); -}, 'calling next() after return() should reject'); - for (const preventCancel of [false, true]) { test(() => { const rs = new ReadableStream(); - rs.getIterator({ preventCancel }).return(); + rs.values({ preventCancel }).return(); // The test passes if this line doesn't throw. rs.getReader(); }, `return() should unlock the stream synchronously when preventCancel = ${preventCancel}`); diff --git a/streams/readable-streams/brand-checks.any.js b/streams/readable-streams/brand-checks.any.js deleted file mode 100644 index dffb5fa74f8dd0..00000000000000 --- a/streams/readable-streams/brand-checks.any.js +++ /dev/null @@ -1,209 +0,0 @@ -// META: global=window,worker,jsshell -// META: script=../resources/test-utils.js -'use strict'; - -let ReadableStreamDefaultReader; -let ReadableStreamDefaultController; -let ReadableStreamAsyncIteratorPrototype; - -test(() => { - - // It's not exposed globally, but we test a few of its properties here. - ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor; - -}, 'Can get the ReadableStreamDefaultReader constructor indirectly'); - -test(() => { - - // It's not exposed globally, but we test a few of its properties here. - new ReadableStream({ - start(c) { - ReadableStreamDefaultController = c.constructor; - } - }); - -}, 'Can get the ReadableStreamDefaultController constructor indirectly'); - -test(() => { - - const rs = new ReadableStream(); - ReadableStreamAsyncIteratorPrototype = Object.getPrototypeOf(rs.getIterator()); - -}, 'Can get ReadableStreamAsyncIteratorPrototype object indirectly'); - -function fakeRS() { - return Object.setPrototypeOf({ - cancel() { return Promise.resolve(); }, - getReader() { return new ReadableStreamDefaultReader(new ReadableStream()); }, - pipeThrough(obj) { return obj.readable; }, - pipeTo() { return Promise.resolve(); }, - tee() { return [realRS(), realRS()]; } - }, ReadableStream.prototype); -} - -function realRS() { - return new ReadableStream(); -} - -function fakeRSDefaultReader() { - return Object.setPrototypeOf({ - get closed() { return Promise.resolve(); }, - cancel() { return Promise.resolve(); }, - read() { return Promise.resolve({ value: undefined, done: true }); }, - releaseLock() { return; } - }, ReadableStreamDefaultReader.prototype); -} - -function realRSDefaultReader() { - return new ReadableStream().getReader(); -} - -function fakeRSDefaultController() { - return Object.setPrototypeOf({ - close() { }, - enqueue() { }, - error() { } - }, ReadableStreamDefaultController.prototype); -} - -function realRSDefaultController() { - let controller; - new ReadableStream({ - start(c) { - controller = c; - } - }); - return controller; -} - -function fakeRSAsyncIterator() { - return Object.setPrototypeOf({ - next() { }, - return(value = undefined) { } - }, ReadableStreamAsyncIteratorPrototype); -} - -promise_test(t => { - - return methodRejectsForAll(t, ReadableStream.prototype, 'cancel', - [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStream.prototype.cancel enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStream.prototype, 'getIterator', - [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStream.prototype.getIterator enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStream.prototype, 'getReader', - [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStream.prototype.getReader enforces a brand check'); - -test(() => { - - getterThrowsForAll(ReadableStream.prototype, 'locked', - [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStream.prototype.locked enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStream.prototype, 'tee', [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStream.prototype.tee enforces a brand check'); - -test(() => { - - assert_throws_js(TypeError, () => new ReadableStreamDefaultReader(fakeRS()), - 'Constructing a ReadableStreamDefaultReader should throw'); - -}, 'ReadableStreamDefaultReader enforces a brand check on its argument'); - -promise_test(t => { - - return getterRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'closed', - [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStreamDefaultReader.prototype.closed enforces a brand check'); - -promise_test(t => { - - return methodRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'cancel', - [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStreamDefaultReader.prototype.cancel enforces a brand check'); - -promise_test(t => { - - return methodRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'read', - [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStreamDefaultReader.prototype.read enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStreamDefaultReader.prototype, 'releaseLock', - [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]); - -}, 'ReadableStreamDefaultReader.prototype.releaseLock enforces a brand check'); - -test(() => { - - assert_throws_js(TypeError, () => new ReadableStreamDefaultController(fakeRS()), - 'Constructing a ReadableStreamDefaultController should throw'); - -}, 'ReadableStreamDefaultController enforces a brand check on its argument'); - -test(() => { - - assert_throws_js(TypeError, () => new ReadableStreamDefaultController(realRS()), - 'Constructing a ReadableStreamDefaultController should throw'); - -}, 'ReadableStreamDefaultController can\'t be given a fully-constructed ReadableStream'); - -test(() => { - - getterThrowsForAll(ReadableStreamDefaultController.prototype, 'desiredSize', - [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]); - -}, 'ReadableStreamDefaultController.prototype.desiredSize enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStreamDefaultController.prototype, 'close', - [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]); - -}, 'ReadableStreamDefaultController.prototype.close enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStreamDefaultController.prototype, 'enqueue', - [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]); - -}, 'ReadableStreamDefaultController.prototype.enqueue enforces a brand check'); - -test(() => { - - methodThrowsForAll(ReadableStreamDefaultController.prototype, 'error', - [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]); - -}, 'ReadableStreamDefaultController.prototype.error enforces a brand check'); - -promise_test(t => { - - return methodRejectsForAll(t, ReadableStreamAsyncIteratorPrototype, 'next', - [fakeRSAsyncIterator(), realRS(), realRSDefaultReader(), undefined, null]); - -}, 'ReadableStreamAsyncIteratorPrototype.next enforces a brand check'); - -promise_test(t => { - - return methodRejectsForAll(t, ReadableStreamAsyncIteratorPrototype, 'return', - [fakeRSAsyncIterator(), realRS(), realRSDefaultReader(), undefined, null]); - -}, 'ReadableStreamAsyncIteratorPrototype.return enforces a brand check'); diff --git a/streams/readable-streams/constructor.any.js b/streams/readable-streams/constructor.any.js index 1ba36899b6387e..dcfd9e9c33861f 100644 --- a/streams/readable-streams/constructor.any.js +++ b/streams/readable-streams/constructor.any.js @@ -1,37 +1,17 @@ // META: global=window,worker,jsshell -// META: script=../resources/constructor-ordering.js 'use strict'; -const operations = [ - op('get', 'size'), - op('get', 'highWaterMark'), - op('get', 'type'), - op('validate', 'type'), - op('validate', 'size'), - op('tonumber', 'highWaterMark'), - op('validate', 'highWaterMark'), - op('get', 'pull'), - op('validate', 'pull'), - op('get', 'cancel'), - op('validate', 'cancel'), - op('get', 'start'), - op('validate', 'start') -]; +const error1 = new Error('error1'); +error1.name = 'error1'; -for (const failureOp of operations) { - test(() => { - const record = new OpRecorder(failureOp); - const underlyingSource = createRecordingObjectWithProperties(record, ['type', 'start', 'pull', 'cancel']); - const strategy = createRecordingStrategy(record); +const error2 = new Error('error2'); +error2.name = 'error2'; - try { - new ReadableStream(underlyingSource, strategy); - assert_unreached('constructor should throw'); - } catch (e) { - assert_equals(typeof e, 'object', 'e should be an object'); - } +test(() => { + const underlyingSource = { get start() { throw error1; } }; + const queuingStrategy = { highWaterMark: 0, get size() { throw error2; } }; - assert_equals(record.actual(), expectedAsString(operations, failureOp), - 'operations should be performed in the right order'); - }, `ReadableStream constructor should stop after ${failureOp} fails`); -} + // underlyingSource is converted in prose in the method body, whereas queuingStrategy is done at the IDL layer. + // So the queuingStrategy exception should be encountered first. + assert_throws_exactly(error2, () => new ReadableStream(underlyingSource, queuingStrategy)); +}, 'underlyingSource argument should be converted after queuingStrategy argument'); diff --git a/streams/readable-streams/default-reader.any.js b/streams/readable-streams/default-reader.any.js index 54abd1844f872a..fc55ebc5b8f678 100644 --- a/streams/readable-streams/default-reader.any.js +++ b/streams/readable-streams/default-reader.any.js @@ -2,15 +2,6 @@ // META: script=../resources/rs-utils.js 'use strict'; -let ReadableStreamDefaultReader; - -test(() => { - - // It's not exposed globally, but we test a few of its properties here. - ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor; - -}, 'Can get the ReadableStreamDefaultReader constructor indirectly'); - test(() => { assert_throws_js(TypeError, () => new ReadableStreamDefaultReader('potato')); @@ -19,44 +10,6 @@ test(() => { }, 'ReadableStreamDefaultReader constructor should get a ReadableStream object as argument'); -test(() => { - - const methods = ['cancel', 'constructor', 'read', 'releaseLock']; - const properties = methods.concat(['closed']).sort(); - - const rsReader = new ReadableStreamDefaultReader(new ReadableStream()); - const proto = Object.getPrototypeOf(rsReader); - - assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties); - - for (const m of methods) { - const propDesc = Object.getOwnPropertyDescriptor(proto, m); - assert_equals(propDesc.enumerable, false, 'method should be non-enumerable'); - assert_equals(propDesc.configurable, true, 'method should be configurable'); - assert_equals(propDesc.writable, true, 'method should be writable'); - assert_equals(typeof rsReader[m], 'function', 'should have be a method'); - const expectedName = m === 'constructor' ? 'ReadableStreamDefaultReader' : m; - assert_equals(rsReader[m].name, expectedName, 'method should have the correct name'); - } - - const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed'); - assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable'); - assert_equals(closedPropDesc.configurable, true, 'closed should be configurable'); - assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter'); - assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter'); - - assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter'); - assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property'); - assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable'); - assert_equals(typeof rsReader.constructor, 'function', 'has a constructor method'); - assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter'); - assert_equals(typeof rsReader.read, 'function', 'has a getReader method'); - assert_equals(rsReader.read.length, 0, 'read has no parameters'); - assert_equals(typeof rsReader.releaseLock, 'function', 'has a releaseLock method'); - assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters'); - -}, 'ReadableStreamDefaultReader instances should have the correct list of properties'); - test(() => { const rsReader = new ReadableStreamDefaultReader(new ReadableStream()); @@ -491,7 +444,7 @@ test(() => { return ''; } }; - assert_throws_js(RangeError, () => rs.getReader({ mode }), 'getReader() should throw'); + assert_throws_js(TypeError, () => rs.getReader({ mode }), 'getReader() should throw'); assert_true(toStringCalled, 'toString() should be called'); }, 'getReader() should call ToString() on mode'); diff --git a/streams/readable-streams/general.any.js b/streams/readable-streams/general.any.js index 48fd8d0b81a339..86b793fe16c932 100644 --- a/streams/readable-streams/general.any.js +++ b/streams/readable-streams/general.any.js @@ -26,64 +26,25 @@ test(() => { test(() => { - assert_throws_js(RangeError, () => new ReadableStream({ type: null }), + assert_throws_js(TypeError, () => new ReadableStream({ type: null }), 'constructor should throw when the type is null'); - assert_throws_js(RangeError, () => new ReadableStream({ type: '' }), + assert_throws_js(TypeError, () => new ReadableStream({ type: '' }), 'constructor should throw when the type is empty string'); - assert_throws_js(RangeError, () => new ReadableStream({ type: 'asdf' }), + assert_throws_js(TypeError, () => new ReadableStream({ type: 'asdf' }), 'constructor should throw when the type is asdf'); - assert_throws_exactly(error1, () => new ReadableStream({ type: { get toString() {throw error1;} } }), 'constructor should throw when ToString() throws'); - assert_throws_exactly(error1, () => new ReadableStream({ type: { toString() {throw error1;} } }), 'constructor should throw when ToString() throws'); + assert_throws_exactly( + error1, + () => new ReadableStream({ type: { get toString() { throw error1; } } }), + 'constructor should throw when ToString() throws' + ); + assert_throws_exactly( + error1, + () => new ReadableStream({ type: { toString() { throw error1; } } }), + 'constructor should throw when ToString() throws' + ); }, 'ReadableStream can\'t be constructed with an invalid type'); -test(() => { - - const methods = ['cancel', 'constructor', 'getReader', 'pipeThrough', 'pipeTo', 'tee', 'getIterator']; - const properties = methods.concat(['locked']).sort(); - const symbols = [Symbol.asyncIterator]; - - const rs = new ReadableStream(); - const proto = Object.getPrototypeOf(rs); - - assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, 'should have all the correct properties'); - assert_array_equals(Object.getOwnPropertySymbols(proto).sort(), symbols, 'should have all the correct symbols'); - - for (const m of methods) { - const propDesc = Object.getOwnPropertyDescriptor(proto, m); - assert_false(propDesc.enumerable, 'method should be non-enumerable'); - assert_true(propDesc.configurable, 'method should be configurable'); - assert_true(propDesc.writable, 'method should be writable'); - assert_equals(typeof rs[m], 'function', 'method should be a function'); - const expectedName = m === 'constructor' ? 'ReadableStream' : m; - assert_equals(rs[m].name, expectedName, 'method should have the correct name'); - } - - const lockedPropDesc = Object.getOwnPropertyDescriptor(proto, 'locked'); - assert_false(lockedPropDesc.enumerable, 'locked should be non-enumerable'); - assert_equals(lockedPropDesc.writable, undefined, 'locked should not be a data property'); - assert_equals(typeof lockedPropDesc.get, 'function', 'locked should have a getter'); - assert_equals(lockedPropDesc.set, undefined, 'locked should not have a setter'); - assert_true(lockedPropDesc.configurable, 'locked should be configurable'); - - assert_equals(rs.cancel.length, 1, 'cancel should have 1 parameter'); - assert_equals(rs.constructor.length, 0, 'constructor should have no parameters'); - assert_equals(rs.getReader.length, 0, 'getReader should have no parameters'); - assert_equals(rs.pipeThrough.length, 1, 'pipeThrough should have 1 parameters'); - assert_equals(rs.pipeTo.length, 1, 'pipeTo should have 1 parameter'); - assert_equals(rs.tee.length, 0, 'tee should have no parameters'); - assert_equals(rs.getIterator.length, 0, 'getIterator should have no required parameters'); - assert_equals(rs[Symbol.asyncIterator].length, 0, '@@asyncIterator should have no required parameters'); - - const asyncIteratorPropDesc = Object.getOwnPropertyDescriptor(proto, Symbol.asyncIterator); - assert_false(asyncIteratorPropDesc.enumerable, '@@asyncIterator should be non-enumerable'); - assert_true(asyncIteratorPropDesc.configurable, '@@asyncIterator should be configurable'); - assert_true(asyncIteratorPropDesc.writable, '@@asyncIterator should be writable'); - assert_equals(typeof rs[Symbol.asyncIterator], 'function', '@@asyncIterator should be a function'); - assert_equals(rs[Symbol.asyncIterator].name, 'getIterator', '@@asyncIterator should have the correct name'); - -}, 'ReadableStream instances should have the correct list of properties'); - test(() => { assert_throws_js(TypeError, () => { @@ -109,38 +70,8 @@ test(() => { let startCalled = false; const source = { - start(controller) { + start() { assert_equals(this, source, 'source is this during start'); - - const methods = ['close', 'enqueue', 'error', 'constructor']; - const properties = ['desiredSize'].concat(methods).sort(); - const proto = Object.getPrototypeOf(controller); - - assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, - 'the controller should have the right properties'); - - for (const m of methods) { - const propDesc = Object.getOwnPropertyDescriptor(proto, m); - assert_equals(typeof controller[m], 'function', `should have a ${m} method`); - assert_false(propDesc.enumerable, m + ' should be non-enumerable'); - assert_true(propDesc.configurable, m + ' should be configurable'); - assert_true(propDesc.writable, m + ' should be writable'); - const expectedName = m === 'constructor' ? 'ReadableStreamDefaultController' : m; - assert_equals(controller[m].name, expectedName, 'method should have the correct name'); - } - - const desiredSizePropDesc = Object.getOwnPropertyDescriptor(proto, 'desiredSize'); - assert_false(desiredSizePropDesc.enumerable, 'desiredSize should be non-enumerable'); - assert_equals(desiredSizePropDesc.writable, undefined, 'desiredSize should not be a data property'); - assert_equals(typeof desiredSizePropDesc.get, 'function', 'desiredSize should have a getter'); - assert_equals(desiredSizePropDesc.set, undefined, 'desiredSize should not have a setter'); - assert_true(desiredSizePropDesc.configurable, 'desiredSize should be configurable'); - - assert_equals(controller.close.length, 0, 'close should have no parameters'); - assert_equals(controller.constructor.length, 0, 'constructor should have no parameters'); - assert_equals(controller.enqueue.length, 1, 'enqueue should have 1 parameter'); - assert_equals(controller.error.length, 1, 'error should have 1 parameter'); - startCalled = true; } }; @@ -148,7 +79,7 @@ test(() => { new ReadableStream(source); assert_true(startCalled); -}, 'ReadableStream start should be called with the proper parameters'); +}, 'ReadableStream start should be called with the proper thisArg'); test(() => { @@ -178,7 +109,7 @@ test(() => { (new ReadableStream()).getReader(undefined); (new ReadableStream()).getReader({}); (new ReadableStream()).getReader({ mode: undefined, notmode: 'ignored' }); - assert_throws_js(RangeError, () => (new ReadableStream()).getReader({ mode: 'potato' })); + assert_throws_js(TypeError, () => (new ReadableStream()).getReader({ mode: 'potato' })); }, 'default ReadableStream getReader() should only accept mode:undefined'); promise_test(() => { @@ -296,18 +227,14 @@ promise_test(() => { promise_test(() => { let pullCount = 0; - const startPromise = Promise.resolve(); new ReadableStream({ - start() { - return startPromise; - }, pull() { pullCount++; } }); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { assert_equals(pullCount, 1, 'pull should be called once start finishes'); return delay(10); }).then(() => { @@ -319,12 +246,8 @@ promise_test(() => { promise_test(() => { let pullCount = 0; - const startPromise = Promise.resolve(); const rs = new ReadableStream({ - start() { - return startPromise; - }, pull(c) { // Don't enqueue immediately after start. We want the stream to be empty when we call .read() on it. if (pullCount > 0) { @@ -334,7 +257,7 @@ promise_test(() => { } }); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { assert_equals(pullCount, 1, 'pull should be called once start finishes'); }).then(() => { const reader = rs.getReader(); @@ -351,12 +274,10 @@ promise_test(() => { promise_test(() => { let pullCount = 0; - const startPromise = Promise.resolve(); const rs = new ReadableStream({ start(c) { c.enqueue('a'); - return startPromise; }, pull() { pullCount++; @@ -366,7 +287,7 @@ promise_test(() => { const read = rs.getReader().read(); assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet'); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { assert_equals(pullCount, 1, 'pull should be called once start finishes'); return read; }).then(r => { @@ -387,14 +308,13 @@ promise_test(() => { const rs = new ReadableStream({ start(c) { c.enqueue('a'); - return startPromise; }, pull() { pullCount++; } }); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { assert_equals(pullCount, 0, 'pull should not be called once start finishes, since the queue is full'); const read = rs.getReader().read(); @@ -413,12 +333,10 @@ promise_test(() => { let pullCount = 0; let controller; - const startPromise = Promise.resolve(); const rs = new ReadableStream({ start(c) { controller = c; - return startPromise; }, pull() { ++pullCount; @@ -426,7 +344,7 @@ promise_test(() => { }); const reader = rs.getReader(); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts'); controller.enqueue('a'); @@ -446,12 +364,10 @@ promise_test(() => { let pullCount = 0; let controller; - const startPromise = Promise.resolve(); const rs = new ReadableStream({ start(c) { controller = c; - return startPromise; }, pull() { ++pullCount; @@ -460,7 +376,7 @@ promise_test(() => { const reader = rs.getReader(); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts'); controller.enqueue('a'); @@ -484,12 +400,8 @@ promise_test(() => { let resolve; let returnedPromise; let timesCalled = 0; - const startPromise = Promise.resolve(); const rs = new ReadableStream({ - start() { - return startPromise; - }, pull(c) { c.enqueue(++timesCalled); returnedPromise = new Promise(r => resolve = r); @@ -498,9 +410,8 @@ promise_test(() => { }); const reader = rs.getReader(); - return startPromise.then(() => { - return reader.read(); - }).then(result1 => { + return reader.read() + .then(result1 => { assert_equals(timesCalled, 1, 'pull should have been called once after start, but not yet have been called a second time'); assert_object_equals(result1, { value: 1, done: false }, 'read() should fulfill with the enqueued value'); @@ -521,7 +432,6 @@ promise_test(() => { promise_test(() => { let timesCalled = 0; - const startPromise = Promise.resolve(); const rs = new ReadableStream( { @@ -529,7 +439,6 @@ promise_test(() => { c.enqueue('a'); c.enqueue('b'); c.enqueue('c'); - return startPromise; }, pull() { ++timesCalled; @@ -544,7 +453,7 @@ promise_test(() => { ); const reader = rs.getReader(); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { return reader.read(); }).then(result1 => { assert_object_equals(result1, { value: 'a', done: false }, 'first chunk should be as expected'); diff --git a/streams/readable-streams/patched-global.any.js b/streams/readable-streams/patched-global.any.js index 9136ba0cc685df..576a39f6777e2b 100644 --- a/streams/readable-streams/patched-global.any.js +++ b/streams/readable-streams/patched-global.any.js @@ -106,7 +106,8 @@ promise_test(async t => { const reader = oldReadableStreamGetReader.call(rs); // stream should be cancelled await reader.closed; -}, 'ReadableStream getIterator() should use the original values of getReader() and ReadableStreamDefaultReader methods'); +}, 'ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader ' + + 'methods'); test(t => { const oldPromiseThen = Promise.prototype.then; diff --git a/streams/resources/constructor-ordering.js b/streams/resources/constructor-ordering.js deleted file mode 100644 index 79862e044c079f..00000000000000 --- a/streams/resources/constructor-ordering.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict'; - -// Helpers for tests that constructors perform getting and validation of properties in the standard order. -// See ../readable-streams/constructor.js for an example of how to use them. - -// Describes an operation on a property. |type| is "get", "validate" or "tonumber". |name| is the name of the property -// in question. |side| is usually undefined, but is used by TransformStream to distinguish between the readable and -// writable strategies. -class Op { - constructor(type, name, side) { - this.type = type; - this.name = name; - this.side = side; - } - - toString() { - return this.side === undefined ? `${this.type} on ${this.name}` : `${this.type} on ${this.name} (${this.side})`; - } - - equals(otherOp) { - return this.type === otherOp.type && this.name === otherOp.name && this.side === otherOp.side; - } -} - -// Provides a concise syntax to create an Op object. |side| is used by TransformStream to distinguish between the two -// strategies. -function op(type, name, side = undefined) { - return new Op(type, name, side); -} - -// Records a sequence of operations. Also checks each operation against |failureOp| to see if it should fail. -class OpRecorder { - constructor(failureOp) { - this.ops = []; - this.failureOp = failureOp; - this.matched = false; - } - - // Record an operation. Returns true if this operation should fail. - recordAndCheck(type, name, side = undefined) { - const recordedOp = op(type, name, side); - this.ops.push(recordedOp); - return this.failureOp.equals(recordedOp); - } - - // Returns true if validation of this property should fail. - check(name, side = undefined) { - return this.failureOp.equals(op('validate', name, side)); - } - - // Returns the sequence of recorded operations as a string. - actual() { - return this.ops.toString(); - } -} - -// Creates an object with the list of properties named in |properties|. Every property access will be recorded in -// |record|, which will also be used to determine whether a particular property access should fail, or whether it should -// return an invalid value that will fail validation. -function createRecordingObjectWithProperties(record, properties) { - const recordingObject = {}; - for (const property of properties) { - defineCheckedProperty(record, recordingObject, property, () => record.check(property) ? 'invalid' : undefined); - } - return recordingObject; -} - -// Add a getter to |object| named |property| which throws if op('get', property) should fail, and otherwise calls -// getter() to get the return value. -function defineCheckedProperty(record, object, property, getter) { - Object.defineProperty(object, property, { - get() { - if (record.recordAndCheck('get', property)) { - throw new Error(`intentional failure of get ${property}`); - } - return getter(); - } - }); -} - -// Similar to createRecordingObjectWithProperties(), but with specific functionality for "highWaterMark" so that numeric -// conversion can be recorded. Permits |side| to be specified so that TransformStream can distinguish between its two -// strategies. -function createRecordingStrategy(record, side = undefined) { - return { - get size() { - if (record.recordAndCheck('get', 'size', side)) { - throw new Error(`intentional failure of get size`); - } - return record.check('size', side) ? 'invalid' : undefined; - }, - get highWaterMark() { - if (record.recordAndCheck('get', 'highWaterMark', side)) { - throw new Error(`intentional failure of get highWaterMark`); - } - return createRecordingNumberObject(record, 'highWaterMark', side); - } - }; -} - -// Creates an object which will record when it is converted to a number. It will assert if the conversion is to some -// other type, and will fail if op('tonumber', property, side) is set as the failure step. The object will convert to -1 -// if 'validate' is set as the failure step, and 1 otherwise. -function createRecordingNumberObject(record, property, side = undefined) { - return { - [Symbol.toPrimitive](hint) { - assert_equals(hint, 'number', `hint for ${property} should be 'number'`); - if (record.recordAndCheck('tonumber', property, side)) { - throw new Error(`intentional failure of ${op('tonumber', property, side)}`); - } - return record.check(property, side) ? -1 : 1; - } - }; -} - -// Creates a string from everything in |operations| up to and including |failureOp|. "validate" steps are excluded from -// the output, as we cannot record them except by making them fail. -function expectedAsString(operations, failureOp) { - const expected = []; - for (const step of operations) { - if (step.type !== 'validate') { - expected.push(step); - } - if (step.equals(failureOp)) { - break; - } - } - return expected.toString(); -} diff --git a/streams/resources/rs-test-templates.js b/streams/resources/rs-test-templates.js index 8f8c385a1914e3..700bd9c3ca9f9e 100644 --- a/streams/resources/rs-test-templates.js +++ b/streams/resources/rs-test-templates.js @@ -23,10 +23,13 @@ self.templatedRSEmpty = (label, factory) => { test(() => { const rs = factory(); - assert_throws_js(RangeError, () => rs.getReader({ mode: '' }), 'empty string mode should throw'); - assert_throws_js(RangeError, () => rs.getReader({ mode: null }), 'null mode should throw'); - assert_throws_js(RangeError, () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw'); - assert_throws_js(TypeError, () => rs.getReader(null), 'null should throw'); + assert_throws_js(TypeError, () => rs.getReader({ mode: '' }), 'empty string mode should throw'); + assert_throws_js(TypeError, () => rs.getReader({ mode: null }), 'null mode should throw'); + assert_throws_js(TypeError, () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw'); + assert_throws_js(TypeError, () => rs.getReader(5), '5 should throw'); + + // Should not throw + rs.getReader(null); }, label + ': calling getReader with invalid arguments should throw appropriate errors'); }; diff --git a/streams/transform-streams/brand-checks.any.js b/streams/transform-streams/brand-checks.any.js deleted file mode 100644 index 1f5cb07745256a..00000000000000 --- a/streams/transform-streams/brand-checks.any.js +++ /dev/null @@ -1,74 +0,0 @@ -// META: global=window,worker,jsshell -// META: script=../resources/test-utils.js -'use strict'; - -const TransformStreamDefaultController = getTransformStreamDefaultControllerConstructor(); - -function getTransformStreamDefaultControllerConstructor() { - return realTSDefaultController().constructor; -} - -function fakeTS() { - return Object.setPrototypeOf({ - get readable() { return new ReadableStream(); }, - get writable() { return new WritableStream(); } - }, TransformStream.prototype); -} - -function realTS() { - return new TransformStream(); -} - -function fakeTSDefaultController() { - return Object.setPrototypeOf({ - get desiredSize() { return 1; }, - enqueue() { }, - close() { }, - error() { } - }, TransformStreamDefaultController.prototype); -} - -function realTSDefaultController() { - let controller; - new TransformStream({ - start(c) { - controller = c; - } - }); - return controller; -} - -test(() => { - getterThrowsForAll(TransformStream.prototype, 'readable', - [fakeTS(), realTSDefaultController(), undefined, null]); -}, 'TransformStream.prototype.readable enforces a brand check'); - -test(() => { - getterThrowsForAll(TransformStream.prototype, 'writable', - [fakeTS(), realTSDefaultController(), undefined, null]); -}, 'TransformStream.prototype.writable enforces a brand check'); - -test(() => { - constructorThrowsForAll(TransformStreamDefaultController, - [fakeTS(), realTS(), realTSDefaultController(), undefined, null]); -}, 'TransformStreamDefaultConstructor enforces a brand check and doesn\'t permit independent construction'); - -test(() => { - getterThrowsForAll(TransformStreamDefaultController.prototype, 'desiredSize', - [fakeTSDefaultController(), realTS(), undefined, null]); -}, 'TransformStreamDefaultController.prototype.desiredSize enforces a brand check'); - -test(() => { - methodThrowsForAll(TransformStreamDefaultController.prototype, 'enqueue', - [fakeTSDefaultController(), realTS(), undefined, null]); -}, 'TransformStreamDefaultController.prototype.enqueue enforces a brand check'); - -test(() => { - methodThrowsForAll(TransformStreamDefaultController.prototype, 'terminate', - [fakeTSDefaultController(), realTS(), undefined, null]); -}, 'TransformStreamDefaultController.prototype.terminate enforces a brand check'); - -test(() => { - methodThrowsForAll(TransformStreamDefaultController.prototype, 'error', - [fakeTSDefaultController(), realTS(), undefined, null]); -}, 'TransformStreamDefaultController.prototype.error enforces a brand check'); diff --git a/streams/transform-streams/constructor.any.js b/streams/transform-streams/constructor.any.js deleted file mode 100644 index 7c18afa712a36d..00000000000000 --- a/streams/transform-streams/constructor.any.js +++ /dev/null @@ -1,46 +0,0 @@ -// META: global=window,worker,jsshell -// META: script=../resources/constructor-ordering.js -'use strict'; - -const operations = [ - op('get', 'size', 'writable'), - op('get', 'highWaterMark', 'writable'), - op('get', 'size', 'readable'), - op('get', 'highWaterMark', 'readable'), - op('get', 'writableType'), - op('validate', 'writableType'), - op('validate', 'size', 'writable'), - op('tonumber', 'highWaterMark', 'writable'), - op('validate', 'highWaterMark', 'writable'), - op('get', 'readableType'), - op('validate', 'readableType'), - op('validate', 'size', 'readable'), - op('tonumber', 'highWaterMark', 'readable'), - op('validate', 'highWaterMark', 'readable'), - op('get', 'transform'), - op('validate', 'transform'), - op('get', 'flush'), - op('validate', 'flush'), - op('get', 'start'), - op('validate', 'start') -]; - -for (const failureOp of operations) { - test(() => { - const record = new OpRecorder(failureOp); - const transformer = createRecordingObjectWithProperties( - record, ['readableType', 'writableType', 'start', 'transform', 'flush']); - const writableStrategy = createRecordingStrategy(record, 'writable'); - const readableStrategy = createRecordingStrategy(record, 'readable'); - - try { - new TransformStream(transformer, writableStrategy, readableStrategy); - assert_unreached('constructor should throw'); - } catch (e) { - assert_equals(typeof e, 'object', 'e should be an object'); - } - - assert_equals(record.actual(), expectedAsString(operations, failureOp), - 'operations should be performed in the right order'); - }, `TransformStream constructor should stop after ${failureOp} fails`); -} diff --git a/streams/transform-streams/general.any.js b/streams/transform-streams/general.any.js index 844512003e6f31..d4f2a1d5a29cf6 100644 --- a/streams/transform-streams/general.any.js +++ b/streams/transform-streams/general.any.js @@ -12,31 +12,6 @@ test(() => { new TransformStream({}); }, 'TransformStream can be constructed with no transform function'); -test(() => { - const ts = new TransformStream({ transform() { } }); - const proto = Object.getPrototypeOf(ts); - - const writableStream = Object.getOwnPropertyDescriptor(proto, 'writable'); - assert_true(writableStream !== undefined, 'it has a writable property'); - assert_false(writableStream.enumerable, 'writable should be non-enumerable'); - assert_equals(typeof writableStream.get, 'function', 'writable should have a getter'); - assert_equals(writableStream.set, undefined, 'writable should not have a setter'); - assert_true(writableStream.configurable, 'writable should be configurable'); - assert_true(ts.writable instanceof WritableStream, 'writable is an instance of WritableStream'); - assert_not_equals(WritableStream.prototype.getWriter.call(ts.writable), undefined, - 'writable should pass WritableStream brand check'); - - const readableStream = Object.getOwnPropertyDescriptor(proto, 'readable'); - assert_true(readableStream !== undefined, 'it has a readable property'); - assert_false(readableStream.enumerable, 'readable should be non-enumerable'); - assert_equals(typeof readableStream.get, 'function', 'readable should have a getter'); - assert_equals(readableStream.set, undefined, 'readable should not have a setter'); - assert_true(readableStream.configurable, 'readable should be configurable'); - assert_true(ts.readable instanceof ReadableStream, 'readable is an instance of ReadableStream'); - assert_not_equals(ReadableStream.prototype.getReader.call(ts.readable), undefined, - 'readable should pass ReadableStream brand check'); -}, 'TransformStream instances must have writable and readable properties of the correct types'); - test(() => { const ts = new TransformStream({ transform() { } }); @@ -44,7 +19,6 @@ test(() => { assert_equals(writer.desiredSize, 1, 'writer.desiredSize should be 1'); }, 'TransformStream writable starts in the writable state'); - promise_test(() => { const ts = new TransformStream(); diff --git a/streams/transform-streams/properties.any.js b/streams/transform-streams/properties.any.js index ebc14afabf915f..f2ac482e0de223 100644 --- a/streams/transform-streams/properties.any.js +++ b/streams/transform-streams/properties.any.js @@ -1,119 +1,6 @@ // META: global=window,worker,jsshell 'use strict'; -// The purpose of this file is to test for objects, attributes and arguments that should not exist. -// The test cases are generated from data tables to reduce duplication. - -// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11. -function IsConstructor(o) { - try { - new new Proxy(o, { construct: () => ({}) })(); - return true; - } catch (e) { - return false; - } -} - -test(() => { - assert_equals(self['TransformStreamDefaultController'], undefined, - `TransformStreamDefaultController should not be defined`); -}, `TransformStreamDefaultController should not be exported on the global object`); - -// Now get hold of the symbol so we can test its properties. -self.TransformStreamDefaultController = (() => { - let controller; - new TransformStream({ - start(c) { - controller = c; - } - }); - return controller.constructor; -})(); - -const expected = { - TransformStream: { - constructor: { - type: 'constructor', - length: 0 - }, - readable: { - type: 'getter' - }, - writable: { - type: 'getter' - } - }, - TransformStreamDefaultController: { - constructor: { - type: 'constructor', - length: 0 - }, - desiredSize: { - type: 'getter' - }, - enqueue: { - type: 'method', - length: 1 - }, - error: { - type: 'method', - length: 1 - }, - terminate: { - type: 'method', - length: 0 - } - } -}; - -for (const c in expected) { - const properties = expected[c]; - const prototype = self[c].prototype; - for (const name in properties) { - const fullName = `${c}.prototype.${name}`; - const descriptor = Object.getOwnPropertyDescriptor(prototype, name); - test(() => { - const { configurable, enumerable } = descriptor; - assert_true(configurable, `${name} should be configurable`); - assert_false(enumerable, `${name} should not be enumerable`); - }, `${fullName} should have standard properties`); - const type = properties[name].type; - switch (type) { - case 'getter': - test(() => { - const { writable, get, set } = descriptor; - assert_equals(writable, undefined, `${name} should not be a data descriptor`); - assert_equals(typeof get, 'function', `${name} should have a getter`); - assert_equals(set, undefined, `${name} should not have a setter`); - }, `${fullName} should be a getter`); - break; - - case 'constructor': - case 'method': - test(() => { - assert_true(descriptor.writable, `${name} should be writable`); - assert_equals(typeof prototype[name], 'function', `${name} should be a function`); - assert_equals(prototype[name].length, properties[name].length, - `${name} should take ${properties[name].length} arguments`); - if (type === 'constructor') { - assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`); - assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`); - } else { - assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`); - assert_equals(prototype[name].name, name, `${name}.name should be '${name}`); - } - }, `${fullName} should be a ${type}`); - break; - } - } - test(() => { - const expectedPropertyNames = Object.keys(properties).sort(); - const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort(); - assert_array_equals(actualPropertyNames, expectedPropertyNames, - `${c} properties should match expected properties`); - }, `${c}.prototype should have exactly the expected properties`); -} - const transformerMethods = { start: { length: 1, @@ -159,31 +46,4 @@ for (const method in transformerMethods) { assert_true(methodWasCalled, `${method} should be called`); }); }, `transformer method ${method} should be called even when it's located on the prototype chain`); - - promise_test(t => { - const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions', - 'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys', - 'apply', 'construct']; - const touchedProperties = []; - const handler = { - get: t.step_func((target, property) => { - touchedProperties.push(property); - if (property === 'readableType' || property === 'writableType') { - return undefined; - } - return () => Promise.resolve(); - }) - }; - for (const trap of unreachedTraps) { - handler[trap] = t.unreached_func(`${trap} should not be trapped`); - } - const transformer = new Proxy({}, handler); - const ts = new TransformStream(transformer, undefined, { highWaterMark: Infinity }); - assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'], - 'expected properties should be got'); - return trigger(ts).then(() => { - assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'], - 'no properties should be accessed on method call'); - }); - }, `unexpected properties should not be accessed when calling transformer method ${method}`); } diff --git a/streams/writable-streams/aborting.any.js b/streams/writable-streams/aborting.any.js index f0e9113faae1d2..5c053bab915700 100644 --- a/streams/writable-streams/aborting.any.js +++ b/streams/writable-streams/aborting.any.js @@ -362,7 +362,7 @@ promise_test(t => { write() { return flushAsyncEvents(); } - }, new CountQueuingStrategy(4)); + }, new CountQueuingStrategy({ highWaterMark: 4 })); const writer = ws.getWriter(); return writer.ready.then(() => { const settlementOrder = []; @@ -382,7 +382,7 @@ promise_test(t => { write() { return Promise.reject(error1); } - }, new CountQueuingStrategy(4)); + }, new CountQueuingStrategy({ highWaterMark: 4 })); const writer = ws.getWriter(); return writer.ready.then(() => { const settlementOrder = []; diff --git a/streams/writable-streams/bad-underlying-sinks.any.js b/streams/writable-streams/bad-underlying-sinks.any.js index ad33ebd82bfbdb..cf04e01b0c0c4d 100644 --- a/streams/writable-streams/bad-underlying-sinks.any.js +++ b/streams/writable-streams/bad-underlying-sinks.any.js @@ -102,12 +102,8 @@ promise_test(t => { promise_test(t => { - const startPromise = Promise.resolve(); let rejectSinkWritePromise; const ws = recordingWritableStream({ - start() { - return startPromise; - }, write() { return new Promise((r, reject) => { rejectSinkWritePromise = reject; @@ -115,7 +111,7 @@ promise_test(t => { } }); - return startPromise.then(() => { + return flushAsyncEvents().then(() => { const writer = ws.getWriter(); const writePromise = writer.write('a'); rejectSinkWritePromise(error1); diff --git a/streams/writable-streams/brand-checks.any.js b/streams/writable-streams/brand-checks.any.js deleted file mode 100644 index 8e2ed77cb3521c..00000000000000 --- a/streams/writable-streams/brand-checks.any.js +++ /dev/null @@ -1,120 +0,0 @@ -// META: global=window,worker,jsshell -// META: script=../resources/test-utils.js -'use strict'; - -const WritableStreamDefaultWriter = new WritableStream().getWriter().constructor; -const WriterProto = WritableStreamDefaultWriter.prototype; -const WritableStreamDefaultController = getWritableStreamDefaultControllerConstructor(); - -function getWritableStreamDefaultControllerConstructor() { - return realWSDefaultController().constructor; -} - -function fakeWS() { - return Object.setPrototypeOf({ - get locked() { return false; }, - abort() { return Promise.resolve(); }, - close() { return Promise.resolve(); }, - getWriter() { return fakeWSDefaultWriter(); } - }, WritableStream.prototype); -} - -function realWS() { - return new WritableStream(); -} - -function fakeWSDefaultWriter() { - return Object.setPrototypeOf({ - get closed() { return Promise.resolve(); }, - get desiredSize() { return 1; }, - get ready() { return Promise.resolve(); }, - abort() { return Promise.resolve(); }, - close() { return Promise.resolve(); }, - write() { return Promise.resolve(); } - }, WritableStreamDefaultWriter.prototype); -} - -function realWSDefaultWriter() { - const ws = new WritableStream(); - return ws.getWriter(); -} - -function fakeWSDefaultController() { - return Object.setPrototypeOf({ - error() { return Promise.resolve(); } - }, WritableStreamDefaultController.prototype); -} - -function realWSDefaultController() { - let controller; - new WritableStream({ - start(c) { - controller = c; - } - }); - return controller; -} - -test(() => { - getterThrowsForAll(WritableStream.prototype, 'locked', - [fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]); -}, 'WritableStream.prototype.locked enforces a brand check'); - -promise_test(t => { - return methodRejectsForAll(t, WritableStream.prototype, 'abort', - [fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]); -}, 'WritableStream.prototype.abort enforces a brand check'); - -promise_test(t => { - return methodRejectsForAll(t, WritableStream.prototype, 'close', - [fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]); -}, 'WritableStream.prototype.close enforces a brand check'); - -test(() => { - methodThrowsForAll(WritableStream.prototype, 'getWriter', - [fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]); -}, 'WritableStream.prototype.getWriter enforces a brand check'); - -test(() => { - assert_throws_js(TypeError, () => new WritableStreamDefaultWriter(fakeWS()), 'constructor should throw'); -}, 'WritableStreamDefaultWriter constructor enforces a brand check'); - -test(() => { - getterThrowsForAll(WriterProto, 'desiredSize', - [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]); -}, 'WritableStreamDefaultWriter.prototype.desiredSize enforces a brand check'); - -promise_test(t => { - return getterRejectsForAll(t, WriterProto, 'closed', - [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]); -}, 'WritableStreamDefaultWriter.prototype.closed enforces a brand check'); - -promise_test(t => { - return getterRejectsForAll(t, WriterProto, 'ready', - [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]); -}, 'WritableStreamDefaultWriter.prototype.ready enforces a brand check'); - -promise_test(t => { - return methodRejectsForAll(t, WriterProto, 'abort', - [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]); -}, 'WritableStreamDefaultWriter.prototype.abort enforces a brand check'); - -promise_test(t => { - return methodRejectsForAll(t, WriterProto, 'write', - [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]); -}, 'WritableStreamDefaultWriter.prototype.write enforces a brand check'); - -promise_test(t => { - return methodRejectsForAll(t, WriterProto, 'close', - [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]); -}, 'WritableStreamDefaultWriter.prototype.close enforces a brand check'); - -test(() => { - methodThrowsForAll(WriterProto, 'releaseLock', - [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]); -}, 'WritableStreamDefaultWriter.prototype.releaseLock enforces a brand check'); - -test(() => { - methodThrowsForAll(WritableStreamDefaultController.prototype, 'error', - [fakeWSDefaultController(), realWS(), realWSDefaultWriter(), undefined, null]); -}, 'WritableStreamDefaultController.prototype.error enforces a brand check'); diff --git a/streams/writable-streams/constructor.any.js b/streams/writable-streams/constructor.any.js index 9bde10b53282db..75eed2a993fe5e 100644 --- a/streams/writable-streams/constructor.any.js +++ b/streams/writable-streams/constructor.any.js @@ -1,10 +1,12 @@ // META: global=window,worker,jsshell -// META: script=../resources/constructor-ordering.js 'use strict'; const error1 = new Error('error1'); error1.name = 'error1'; +const error2 = new Error('error2'); +error2.name = 'error2'; + promise_test(() => { let controller; const ws = new WritableStream({ @@ -85,6 +87,15 @@ test(() => { new WritableStream(); }, 'WritableStream should be constructible with no arguments'); +test(() => { + const underlyingSink = { get start() { throw error1; } }; + const queuingStrategy = { highWaterMark: 0, get size() { throw error2; } }; + + // underlyingSink is converted in prose in the method body, whereas queuingStrategy is done at the IDL layer. + // So the queuingStrategy exception should be encountered first. + assert_throws_exactly(error2, () => new WritableStream(underlyingSink, queuingStrategy)); +}, 'underlyingSink argument should be converted after queuingStrategy argument'); + test(() => { const ws = new WritableStream({}); @@ -102,11 +113,6 @@ test(() => { assert_equals(typeof writer.closed.then, 'function', 'closed property should be thenable'); }, 'WritableStream instances should have standard methods and properties'); -test(() => { - ['WritableStreamDefaultWriter', 'WritableStreamDefaultController'].forEach(c => - assert_equals(typeof self[c], 'undefined', `${c} should not be exported`)); -}, 'private constructors should not be exported'); - test(() => { let WritableStreamDefaultController; new WritableStream({ @@ -147,39 +153,3 @@ test(() => { assert_throws_js(TypeError, () => new WritableStreamDefaultWriter(stream), 'constructor should throw a TypeError exception'); }, 'WritableStreamDefaultWriter constructor should throw when stream argument is locked'); - -const operations = [ - op('get', 'size'), - op('get', 'highWaterMark'), - op('get', 'type'), - op('validate', 'type'), - op('validate', 'size'), - op('tonumber', 'highWaterMark'), - op('validate', 'highWaterMark'), - op('get', 'write'), - op('validate', 'write'), - op('get', 'close'), - op('validate', 'close'), - op('get', 'abort'), - op('validate', 'abort'), - op('get', 'start'), - op('validate', 'start') -]; - -for (const failureOp of operations) { - test(() => { - const record = new OpRecorder(failureOp); - const underlyingSink = createRecordingObjectWithProperties(record, ['type', 'start', 'write', 'close', 'abort']); - const strategy = createRecordingStrategy(record); - - try { - new WritableStream(underlyingSink, strategy); - assert_unreached('constructor should throw'); - } catch (e) { - assert_equals(typeof e, 'object', 'e should be an object'); - } - - assert_equals(record.actual(), expectedAsString(operations, failureOp), - 'operations should be performed in the right order'); - }, `WritableStream constructor should stop after ${failureOp} fails`); -} diff --git a/streams/writable-streams/properties.any.js b/streams/writable-streams/properties.any.js index dadb5ed21e6c30..0f7f876d8b6fc4 100644 --- a/streams/writable-streams/properties.any.js +++ b/streams/writable-streams/properties.any.js @@ -1,150 +1,6 @@ // META: global=window,worker,jsshell 'use strict'; -// The purpose of this file is to test for objects, attributes and arguments that should not exist. -// The test cases are generated from data tables to reduce duplication. - -// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11. -function IsConstructor(o) { - try { - new new Proxy(o, { construct: () => ({}) })(); - return true; - } catch (e) { - return false; - } -} - -for (const func of ['WritableStreamDefaultController', 'WritableStreamDefaultWriter']) { - test(() => { - assert_equals(self[func], undefined, `${func} should not be defined`); - }, `${func} should not be exported on the global object`); -} - -// Now get hold of the symbols so we can test their properties. -self.WritableStreamDefaultController = (() => { - let controller; - new WritableStream({ - start(c) { - controller = c; - } - }); - return controller.constructor; -})(); -self.WritableStreamDefaultWriter = new WritableStream().getWriter().constructor; - -const expected = { - WritableStream: { - constructor: { - type: 'constructor', - length: 0 - }, - locked: { - type: 'getter' - }, - abort: { - type: 'method', - length: 1 - }, - close: { - type: 'method', - length: 0 - }, - getWriter: { - type: 'method', - length: 0 - } - }, - WritableStreamDefaultController: { - constructor: { - type: 'constructor', - length: 0 - }, - error: { - type: 'method', - length: 1 - } - }, - WritableStreamDefaultWriter: { - constructor: { - type: 'constructor', - length: 1 - }, - closed: { - type: 'getter' - }, - desiredSize: { - type: 'getter' - }, - ready: { - type: 'getter' - }, - abort: { - type: 'method', - length: 1 - }, - close: { - type: 'method', - length: 0 - }, - releaseLock: { - type: 'method', - length: 0 - }, - write: { - type: 'method', - length: 1 - } - } -}; - -for (const c in expected) { - const properties = expected[c]; - const prototype = self[c].prototype; - for (const name in properties) { - const fullName = `${c}.prototype.${name}`; - const descriptor = Object.getOwnPropertyDescriptor(prototype, name); - test(() => { - const { configurable, enumerable } = descriptor; - assert_true(configurable, `${name} should be configurable`); - assert_false(enumerable, `${name} should not be enumerable`); - }, `${fullName} should have standard properties`); - const type = properties[name].type; - switch (type) { - case 'getter': - test(() => { - const { writable, get, set } = descriptor; - assert_equals(writable, undefined, `${name} should not be a data descriptor`); - assert_equals(typeof get, 'function', `${name} should have a getter`); - assert_equals(set, undefined, `${name} should not have a setter`); - }, `${fullName} should be a getter`); - break; - - case 'constructor': - case 'method': - test(() => { - assert_true(descriptor.writable, `${name} should be writable`); - assert_equals(typeof prototype[name], 'function', `${name} should be a function`); - assert_equals(prototype[name].length, properties[name].length, - `${name} should take ${properties[name].length} arguments`); - if (type === 'constructor') { - assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`); - assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`); - } else { - assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`); - assert_equals(prototype[name].name, name, `${name}.name should be '${name}`); - } - }, `${fullName} should be a ${type}`); - break; - } - } - test(() => { - const expectedPropertyNames = Object.keys(properties).sort(); - const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort(); - assert_array_equals(actualPropertyNames, expectedPropertyNames, - `${c} properties should match expected properties`); - }, `${c}.prototype should have exactly the expected properties`); -} - const sinkMethods = { start: { length: 1, @@ -194,31 +50,4 @@ for (const method in sinkMethods) { assert_true(methodWasCalled, `${method} should be called`); }); }, `sink method ${method} should be called even when it's located on the prototype chain`); - - promise_test(t => { - const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions', - 'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys', - 'apply', 'construct']; - const touchedProperties = []; - const handler = { - get: t.step_func((target, property) => { - touchedProperties.push(property); - if (property === 'type') { - return undefined; - } - return () => Promise.resolve(); - }) - }; - for (const trap of unreachedTraps) { - handler[trap] = t.unreached_func(`${trap} should not be trapped`); - } - const sink = new Proxy({}, handler); - const ws = new WritableStream(sink); - assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'], - 'expected properties should be got'); - return trigger(ws.getWriter()).then(() => { - assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'], - 'no properties should be accessed on method call'); - }); - }, `unexpected properties should not be accessed when calling sink method ${method}`); }