diff --git a/Tests/LibWeb/Text/expected/wpt-import/streams/readable-byte-streams/read-min.any.txt b/Tests/LibWeb/Text/expected/wpt-import/streams/readable-byte-streams/read-min.any.txt new file mode 100644 index 00000000000..3425da19bb8 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/streams/readable-byte-streams/read-min.any.txt @@ -0,0 +1,35 @@ +Summary + +Harness status: OK + +Rerun + +Found 24 tests + +23 Pass +1 Fail +Details +Result Test Name MessagePass ReadableStream with byte source: read({ min }) rejects if min is 0 +Fail ReadableStream with byte source: read({ min }) rejects if min is negative +Pass ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint8Array) +Pass ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint16Array) +Pass ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (DataView) +Pass ReadableStream with byte source: read({ min }), then read() +Pass ReadableStream with byte source: read({ min }) with a DataView +Pass ReadableStream with byte source: enqueue(), then read({ min }) +Pass ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes +Pass ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes +Pass ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes +Pass ReadableStream with byte source: enqueue(), read({ min }) partially, then read() +Pass ReadableStream with byte source: read({ min }), then respondWithNewView() with a transferred ArrayBuffer +Pass ReadableStream with byte source: read({ min }) on a closed stream +Pass ReadableStream with byte source: read({ min }) when closed before view is filled +Pass ReadableStream with byte source: read({ min }) when closed immediately after view is filled +Pass ReadableStream with byte source: read({ min }) on an errored stream +Pass ReadableStream with byte source: read({ min }), then error() +Pass ReadableStream with byte source: getReader(), read({ min }), then cancel() +Pass ReadableStream with byte source: cancel() with partially filled pending read({ min }) request +Pass ReadableStream with byte source: enqueue(), then read({ min }) with smaller views +Pass ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail +Pass ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail +Pass ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2 \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/streams/readable-byte-streams/read-min.any.html b/Tests/LibWeb/Text/input/wpt-import/streams/readable-byte-streams/read-min.any.html new file mode 100644 index 00000000000..5690fb36364 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/streams/readable-byte-streams/read-min.any.html @@ -0,0 +1,16 @@ + + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/streams/readable-byte-streams/read-min.any.js b/Tests/LibWeb/Text/input/wpt-import/streams/readable-byte-streams/read-min.any.js new file mode 100644 index 00000000000..4010e3750ce --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/streams/readable-byte-streams/read-min.any.js @@ -0,0 +1,774 @@ +// META: global=window,worker,shadowrealm +// META: script=../resources/rs-utils.js +// META: script=../resources/test-utils.js +'use strict'; + +// View buffers are detached after pull() returns, so record the information at the time that pull() was called. +function extractViewInfo(view) { + return { + constructor: view.constructor, + bufferByteLength: view.buffer.byteLength, + byteOffset: view.byteOffset, + byteLength: view.byteLength + }; +} + +promise_test(async t => { + const rs = new ReadableStream({ + type: 'bytes', + pull: t.unreached_func('pull() should not be called'), + }); + const reader = rs.getReader({ mode: 'byob' }); + await promise_rejects_js(t, TypeError, reader.read(new Uint8Array(1), { min: 0 })); +}, 'ReadableStream with byte source: read({ min }) rejects if min is 0'); + +promise_test(async t => { + const rs = new ReadableStream({ + type: 'bytes', + pull: t.unreached_func('pull() should not be called'), + }); + const reader = rs.getReader({ mode: 'byob' }); + await promise_rejects_js(t, TypeError, reader.read(new Uint8Array(1), { min: -1 })); +}, 'ReadableStream with byte source: read({ min }) rejects if min is negative'); + +promise_test(async t => { + const rs = new ReadableStream({ + type: 'bytes', + pull: t.unreached_func('pull() should not be called'), + }); + const reader = rs.getReader({ mode: 'byob' }); + await promise_rejects_js(t, RangeError, reader.read(new Uint8Array(1), { min: 2 })); +}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (Uint8Array)'); + +promise_test(async t => { + const rs = new ReadableStream({ + type: 'bytes', + pull: t.unreached_func('pull() should not be called'), + }); + const reader = rs.getReader({ mode: 'byob' }); + await promise_rejects_js(t, RangeError, reader.read(new Uint16Array(1), { min: 2 })); +}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (Uint16Array)'); + +promise_test(async t => { + const rs = new ReadableStream({ + type: 'bytes', + pull: t.unreached_func('pull() should not be called'), + }); + const reader = rs.getReader({ mode: 'byob' }); + await promise_rejects_js(t, RangeError, reader.read(new DataView(new ArrayBuffer(1)), { min: 2 })); +}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (DataView)'); + +promise_test(async t => { + let pullCount = 0; + const byobRequests = []; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + const byobRequest = c.byobRequest; + const view = byobRequest.view; + byobRequests[pullCount] = { + nonNull: byobRequest !== null, + viewNonNull: view !== null, + viewInfo: extractViewInfo(view) + }; + if (pullCount === 0) { + view[0] = 0x01; + view[1] = 0x02; + byobRequest.respond(2); + } else if (pullCount === 1) { + view[0] = 0x03; + byobRequest.respond(1); + } else if (pullCount === 2) { + view[0] = 0x04; + byobRequest.respond(1); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + const read1 = reader.read(new Uint8Array(3), { min: 3 }); + const read2 = reader.read(new Uint8Array(1)); + + const result1 = await read1; + assert_false(result1.done, 'first result should not be done'); + assert_typed_array_equals(result1.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value'); + + const result2 = await read2; + assert_false(result2.done, 'second result should not be done'); + assert_typed_array_equals(result2.value, new Uint8Array([0x04]), 'second result value'); + + assert_equals(pullCount, 3, 'pull() must have been called 3 times'); + + { + const byobRequest = byobRequests[0]; + 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, 3, 'first view.buffer.byteLength should be 3'); + assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0'); + assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3'); + } + + { + const byobRequest = byobRequests[1]; + 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, 3, 'second view.buffer.byteLength should be 3'); + assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2'); + assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1'); + } + + { + const byobRequest = byobRequests[2]; + assert_true(byobRequest.nonNull, 'third byobRequest must not be null'); + assert_true(byobRequest.viewNonNull, 'third byobRequest.view must not be null'); + const viewInfo = byobRequest.viewInfo; + assert_equals(viewInfo.constructor, Uint8Array, 'third view.constructor should be Uint8Array'); + assert_equals(viewInfo.bufferByteLength, 1, 'third view.buffer.byteLength should be 1'); + assert_equals(viewInfo.byteOffset, 0, 'third view.byteOffset should be 0'); + assert_equals(viewInfo.byteLength, 1, 'third view.byteLength should be 1'); + } + +}, 'ReadableStream with byte source: read({ min }), then read()'); + +promise_test(async t => { + let pullCount = 0; + const byobRequests = []; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + const byobRequest = c.byobRequest; + const view = byobRequest.view; + byobRequests[pullCount] = { + nonNull: byobRequest !== null, + viewNonNull: view !== null, + viewInfo: extractViewInfo(view) + }; + if (pullCount === 0) { + view[0] = 0x01; + view[1] = 0x02; + byobRequest.respond(2); + } else if (pullCount === 1) { + view[0] = 0x03; + byobRequest.respond(1); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + + const result = await reader.read(new DataView(new ArrayBuffer(3)), { min: 3 }); + assert_false(result.done, 'result should not be done'); + assert_equals(result.value.constructor, DataView, 'result.value must be a DataView'); + assert_equals(result.value.byteOffset, 0, 'result.value.byteOffset'); + assert_equals(result.value.byteLength, 3, 'result.value.byteLength'); + assert_equals(result.value.buffer.byteLength, 3, 'result.value.buffer.byteLength'); + assert_array_equals([...new Uint8Array(result.value.buffer)], [0x01, 0x02, 0x03], `result.value.buffer contents`); + + assert_equals(pullCount, 2, 'pull() must have been called 2 times'); + + { + const byobRequest = byobRequests[0]; + 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, 3, 'first view.buffer.byteLength should be 3'); + assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0'); + assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3'); + } + + { + const byobRequest = byobRequests[1]; + 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, 3, 'second view.buffer.byteLength should be 3'); + assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2'); + assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1'); + } + +}, 'ReadableStream with byte source: read({ min }) with a DataView'); + +promise_test(async t => { + let pullCount = 0; + const byobRequests = []; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + c.enqueue(new Uint8Array([0x01])); + }), + pull: t.step_func((c) => { + const byobRequest = c.byobRequest; + const view = byobRequest.view; + byobRequests[pullCount] = { + nonNull: byobRequest !== null, + viewNonNull: view !== null, + viewInfo: extractViewInfo(view) + }; + if (pullCount === 0) { + view[0] = 0x02; + view[1] = 0x03; + byobRequest.respond(2); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + + const result = await reader.read(new Uint8Array(3), { min: 3 }); + assert_false(result.done, 'first result should not be done'); + assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value'); + + assert_equals(pullCount, 1, 'pull() must have only been called once'); + + const byobRequest = byobRequests[0]; + 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, 3, 'first view.buffer.byteLength should be 3'); + assert_equals(viewInfo.byteOffset, 1, 'first view.byteOffset should be 1'); + assert_equals(viewInfo.byteLength, 2, 'first view.byteLength should be 2'); + +}, 'ReadableStream with byte source: enqueue(), then read({ min })'); + +promise_test(async t => { + let pullCount = 0; + const byobRequests = []; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + const byobRequest = c.byobRequest; + const view = byobRequest.view; + byobRequests[pullCount] = { + nonNull: byobRequest !== null, + viewNonNull: view !== null, + viewInfo: extractViewInfo(view) + }; + if (pullCount === 0) { + c.enqueue(new Uint8Array([0x01, 0x02])); + } else if (pullCount === 1) { + c.enqueue(new Uint8Array([0x03])); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + + const result = await reader.read(new Uint8Array(3), { min: 3 }); + assert_false(result.done, 'first result should not be done'); + assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value'); + + assert_equals(pullCount, 2, 'pull() must have been called 2 times'); + + { + const byobRequest = byobRequests[0]; + 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, 3, 'first view.buffer.byteLength should be 3'); + assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0'); + assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3'); + } + + { + const byobRequest = byobRequests[1]; + 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, 3, 'second view.buffer.byteLength should be 3'); + assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2'); + assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1'); + } + +}, 'ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes'); + +promise_test(async t => { + let pullCount = 0; + const byobRequests = []; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + const byobRequest = c.byobRequest; + const view = byobRequest.view; + byobRequests[pullCount] = { + nonNull: byobRequest !== null, + viewNonNull: view !== null, + viewInfo: extractViewInfo(view) + }; + if (pullCount === 0) { + c.enqueue(new Uint8Array([0x01, 0x02])); + } else if (pullCount === 1) { + c.enqueue(new Uint8Array([0x03])); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + + const result = await reader.read(new Uint8Array(5), { min: 3 }); + assert_false(result.done, 'first result should not be done'); + assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03, 0, 0]).subarray(0, 3), 'first result value'); + + assert_equals(pullCount, 2, 'pull() must have been called 2 times'); + + { + const byobRequest = byobRequests[0]; + 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, 5, 'first view.buffer.byteLength should be 5'); + assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0'); + assert_equals(viewInfo.byteLength, 5, 'first view.byteLength should be 5'); + } + + { + const byobRequest = byobRequests[1]; + 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, 5, 'second view.buffer.byteLength should be 5'); + assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2'); + assert_equals(viewInfo.byteLength, 3, 'second view.byteLength should be 3'); + } + +}, 'ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes'); + +promise_test(async t => { + let pullCount = 0; + const byobRequests = []; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + const byobRequest = c.byobRequest; + const view = byobRequest.view; + byobRequests[pullCount] = { + nonNull: byobRequest !== null, + viewNonNull: view !== null, + viewInfo: extractViewInfo(view) + }; + if (pullCount === 0) { + c.enqueue(new Uint8Array([0x01, 0x02])); + } else if (pullCount === 1) { + c.enqueue(new Uint8Array([0x03, 0x04])); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + + const result = await reader.read(new Uint8Array(5), { min: 3 }); + assert_false(result.done, 'first result should not be done'); + assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03, 0x04, 0]).subarray(0, 4), 'first result value'); + + assert_equals(pullCount, 2, 'pull() must have been called 2 times'); + + { + const byobRequest = byobRequests[0]; + 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, 5, 'first view.buffer.byteLength should be 5'); + assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0'); + assert_equals(viewInfo.byteLength, 5, 'first view.byteLength should be 5'); + } + + { + const byobRequest = byobRequests[1]; + 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, 5, 'second view.buffer.byteLength should be 5'); + assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2'); + assert_equals(viewInfo.byteLength, 3, 'second view.byteLength should be 3'); + } + +}, 'ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes'); + +promise_test(async t => { + const stream = new ReadableStream({ + start(c) { + const view = new Uint8Array(16); + view[0] = 0x01; + view[8] = 0x02; + c.enqueue(view); + }, + pull: t.unreached_func('pull() should not be called'), + type: 'bytes' + }); + + const byobReader = stream.getReader({ mode: 'byob' }); + const result1 = await byobReader.read(new Uint8Array(8), { min: 8 }); + assert_false(result1.done, 'result1.done'); + + const view1 = result1.value; + assert_equals(view1.constructor, Uint8Array, 'result1.value.constructor'); + assert_equals(view1.buffer.byteLength, 8, 'result1.value.buffer.byteLength'); + assert_equals(view1.byteOffset, 0, 'result1.value.byteOffset'); + assert_equals(view1.byteLength, 8, 'result1.value.byteLength'); + assert_equals(view1[0], 0x01, 'result1.value[0]'); + + byobReader.releaseLock(); + + const reader = stream.getReader(); + const result2 = await reader.read(); + assert_false(result2.done, 'result2.done'); + + const view2 = result2.value; + assert_equals(view2.constructor, Uint8Array, 'result2.value.constructor'); + assert_equals(view2.buffer.byteLength, 16, 'result2.value.buffer.byteLength'); + assert_equals(view2.byteOffset, 8, 'result2.value.byteOffset'); + assert_equals(view2.byteLength, 8, 'result2.value.byteLength'); + assert_equals(view2[0], 0x02, 'result2.value[0]'); +}, 'ReadableStream with byte source: enqueue(), read({ min }) partially, then read()'); + +promise_test(async () => { + let pullCount = 0; + const byobRequestDefined = []; + let byobRequestViewDefined; + + const stream = new ReadableStream({ + async pull(c) { + byobRequestDefined.push(c.byobRequest !== null); + const initialByobRequest = c.byobRequest; + + const transferredView = await transferArrayBufferView(c.byobRequest.view); + transferredView[0] = 0x01; + c.byobRequest.respondWithNewView(transferredView); + + byobRequestDefined.push(c.byobRequest !== null); + byobRequestViewDefined = initialByobRequest.view !== null; + + ++pullCount; + }, + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + const result = await reader.read(new Uint8Array(1), { min: 1 }); + assert_false(result.done, 'result.done'); + 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 null before respondWithNewView()'); + assert_false(byobRequestDefined[1], 'byobRequest must be null after respondWithNewView()'); + assert_false(byobRequestViewDefined, 'view of initial byobRequest must be null after respondWithNewView()'); +}, 'ReadableStream with byte source: read({ min }), then respondWithNewView() with a transferred ArrayBuffer'); + +promise_test(async t => { + const stream = new ReadableStream({ + start(c) { + c.close(); + }, + pull: t.unreached_func('pull() should not be called'), + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + + const result = await reader.read(new Uint8Array([0x01]), { min: 1 }); + assert_true(result.done, 'result.done'); + assert_typed_array_equals(result.value, new Uint8Array([0x01]).subarray(0, 0), 'result.value'); + + await reader.closed; +}, 'ReadableStream with byte source: read({ min }) on a closed stream'); + +promise_test(async t => { + let pullCount = 0; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + if (pullCount === 0) { + c.byobRequest.view[0] = 0x01; + c.byobRequest.respond(1); + } else if (pullCount === 1) { + c.close(); + c.byobRequest.respond(0); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + + const result = await reader.read(new Uint8Array(3), { min: 3 }); + assert_true(result.done, 'result.done'); + assert_typed_array_equals(result.value, new Uint8Array([0x01, 0, 0]).subarray(0, 1), 'result.value'); + + assert_equals(pullCount, 2, 'pull() must have been called 2 times'); + + await reader.closed; +}, 'ReadableStream with byte source: read({ min }) when closed before view is filled'); + +promise_test(async t => { + let pullCount = 0; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + if (pullCount === 0) { + c.byobRequest.view[0] = 0x01; + c.byobRequest.view[1] = 0x02; + c.byobRequest.respond(2); + } else if (pullCount === 1) { + c.byobRequest.view[0] = 0x03; + c.byobRequest.respond(1); + c.close(); + } + ++pullCount; + }) + }); + const reader = rs.getReader({ mode: 'byob' }); + + const result = await reader.read(new Uint8Array(3), { min: 3 }); + assert_false(result.done, 'result.done'); + assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'result.value'); + + assert_equals(pullCount, 2, 'pull() must have been called 2 times'); + + await reader.closed; +}, 'ReadableStream with byte source: read({ min }) when closed immediately after view is filled'); + +promise_test(async t => { + const error1 = new Error('error1'); + const stream = new ReadableStream({ + start(c) { + c.error(error1); + }, + pull: t.unreached_func('pull() should not be called'), + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + const read = reader.read(new Uint8Array(1), { min: 1 }); + + await Promise.all([ + promise_rejects_exactly(t, error1, read, 'read() must fail'), + promise_rejects_exactly(t, error1, reader.closed, 'closed must fail') + ]); +}, 'ReadableStream with byte source: read({ min }) on an errored stream'); + +promise_test(async t => { + const error1 = new Error('error1'); + let controller; + const stream = new ReadableStream({ + start(c) { + controller = c; + }, + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + const read = reader.read(new Uint8Array(1), { min: 1 }); + + controller.error(error1); + + await Promise.all([ + promise_rejects_exactly(t, error1, read, 'read() must fail'), + promise_rejects_exactly(t, error1, reader.closed, 'closed must fail') + ]); +}, 'ReadableStream with byte source: read({ min }), then error()'); + +promise_test(t => { + let cancelCount = 0; + let reason; + + const passedReason = new TypeError('foo'); + + const stream = new ReadableStream({ + pull: t.unreached_func('pull() should not be called'), + cancel(r) { + if (cancelCount === 0) { + reason = r; + } + + ++cancelCount; + + return 'bar'; + }, + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + + const readPromise = reader.read(new Uint8Array(1), { min: 1 }).then(result => { + assert_true(result.done, 'result.done'); + assert_equals(result.value, undefined, 'result.value'); + }); + + const cancelPromise = reader.cancel(passedReason).then(result => { + assert_equals(result, undefined, 'cancel() return value should be fulfilled with undefined'); + assert_equals(cancelCount, 1, 'cancel() should be called only once'); + assert_equals(reason, passedReason, 'reason should equal the passed reason'); + }); + + return Promise.all([readPromise, cancelPromise]); +}, 'ReadableStream with byte source: getReader(), read({ min }), then cancel()'); + +promise_test(async t => { + let pullCount = 0; + let byobRequest; + const viewInfos = []; + const rs = new ReadableStream({ + type: 'bytes', + pull: t.step_func((c) => { + byobRequest = c.byobRequest; + + viewInfos.push(extractViewInfo(c.byobRequest.view)); + c.byobRequest.view[0] = 0x01; + c.byobRequest.respond(1); + viewInfos.push(extractViewInfo(c.byobRequest.view)); + + ++pullCount; + }) + }); + + await Promise.resolve(); + assert_equals(pullCount, 0, 'pull() must not have been called yet'); + + const reader = rs.getReader({ mode: 'byob' }); + const read = reader.read(new Uint8Array(3), { min: 3 }); + assert_equals(pullCount, 1, 'pull() must have been called once'); + assert_not_equals(byobRequest, null, 'byobRequest should not be null'); + assert_equals(viewInfos[0].byteLength, 3, 'byteLength before respond() should be 3'); + assert_equals(viewInfos[1].byteLength, 2, 'byteLength after respond() should be 2'); + + reader.cancel().catch(t.unreached_func('cancel() should not reject')); + + const result = await read; + assert_true(result.done, 'result.done'); + assert_equals(result.value, undefined, 'result.value'); + + assert_equals(pullCount, 1, 'pull() must only be called once'); + + await reader.closed; +}, 'ReadableStream with byte source: cancel() with partially filled pending read({ min }) request'); + +promise_test(async () => { + let pullCalled = false; + + const stream = new ReadableStream({ + start(c) { + const view = new Uint8Array(16); + view[7] = 0x01; + view[15] = 0x02; + c.enqueue(view); + }, + pull() { + pullCalled = true; + }, + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + + const result1 = await reader.read(new Uint8Array(8), { min: 8 }); + assert_false(result1.done, 'result1.done'); + + const view1 = result1.value; + assert_equals(view1.byteOffset, 0, 'result1.value.byteOffset'); + assert_equals(view1.byteLength, 8, 'result1.value.byteLength'); + assert_equals(view1[7], 0x01, 'result1.value[7]'); + + const result2 = await reader.read(new Uint8Array(8), { min: 8 }); + assert_false(pullCalled, 'pull() must not have been called'); + assert_false(result2.done, 'result2.done'); + + const view2 = result2.value; + assert_equals(view2.byteOffset, 0, 'result2.value.byteOffset'); + assert_equals(view2.byteLength, 8, 'result2.value.byteLength'); + assert_equals(view2[7], 0x02, 'result2.value[7]'); +}, 'ReadableStream with byte source: enqueue(), then read({ min }) with smaller views'); + +promise_test(async t => { + const stream = new ReadableStream({ + start(c) { + c.enqueue(new Uint8Array([0xaa, 0xbb, 0xcc])); + c.close(); + }, + pull: t.unreached_func('pull() should not be called'), + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + + await promise_rejects_js(t, TypeError, reader.read(new Uint16Array(2), { min: 2 }), 'read() must fail'); + await promise_rejects_js(t, TypeError, reader.closed, 'reader.closed should reject'); +}, 'ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail'); + +promise_test(async t => { + let controller; + const stream = new ReadableStream({ + start(c) { + controller = c; + }, + pull: t.unreached_func('pull() should not be called'), + type: 'bytes' + }); + + const reader = stream.getReader({ mode: 'byob' }); + const readPromise = reader.read(new Uint16Array(2), { min: 2 }); + + controller.enqueue(new Uint8Array([0xaa, 0xbb, 0xcc])); + assert_throws_js(TypeError, () => controller.close(), 'controller.close() must throw'); + + await promise_rejects_js(t, TypeError, readPromise, 'read() must fail'); + await promise_rejects_js(t, TypeError, reader.closed, 'reader.closed must reject'); +}, 'ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail'); + +promise_test(async t => { + let pullCount = 0; + let controller; + const rs = new ReadableStream({ + type: 'bytes', + start: t.step_func((c) => { + controller = c; + }), + pull: t.step_func((c) => { + ++pullCount; + }) + }); + + const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' })); + + await Promise.resolve(); + assert_equals(pullCount, 0, 'pull() must not have been called yet'); + + const read1 = reader1.read(new Uint8Array(3), { min: 3 }); + const read2 = reader2.read(new Uint8Array(1)); + + assert_equals(pullCount, 1, 'pull() must have been called once'); + const byobRequest1 = controller.byobRequest; + assert_equals(byobRequest1.view.byteLength, 3, 'first byobRequest.view.byteLength should be 3'); + byobRequest1.view[0] = 0x01; + byobRequest1.respond(1); + + const result2 = await read2; + assert_false(result2.done, 'branch2 first read() should not be done'); + assert_typed_array_equals(result2.value, new Uint8Array([0x01]), 'branch2 first read() value'); + + assert_equals(pullCount, 2, 'pull() must have been called 2 times'); + const byobRequest2 = controller.byobRequest; + assert_equals(byobRequest2.view.byteLength, 2, 'second byobRequest.view.byteLength should be 2'); + byobRequest2.view[0] = 0x02; + byobRequest2.view[1] = 0x03; + byobRequest2.respond(2); + + const result1 = await read1; + assert_false(result1.done, 'branch1 read() should not be done'); + assert_typed_array_equals(result1.value, new Uint8Array([0x01, 0x02, 0x03]), 'branch1 read() value'); + + const result3 = await reader2.read(new Uint8Array(2)); + assert_equals(pullCount, 2, 'pull() must only be called 2 times'); + assert_false(result3.done, 'branch2 second read() should not be done'); + assert_typed_array_equals(result3.value, new Uint8Array([0x02, 0x03]), 'branch2 second read() value'); +}, 'ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2'); diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp index 5432813bcbb..e6013bda2f9 100644 --- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -872,7 +872,7 @@ public: // 1. Queue a microtask to perform the following steps: HTML::queue_a_microtask(nullptr, JS::create_heap_function(m_realm->heap(), [this, chunk = chunk_view]() { - HTML::TemporaryExecutionContext execution_context { m_realm }; + HTML::TemporaryExecutionContext execution_context { m_realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; auto byob_controller = m_byob_branch->controller()->get