diff options
Diffstat (limited to 'src/js/builtins/ReadableByteStreamInternals.ts')
-rw-r--r-- | src/js/builtins/ReadableByteStreamInternals.ts | 656 |
1 files changed, 656 insertions, 0 deletions
diff --git a/src/js/builtins/ReadableByteStreamInternals.ts b/src/js/builtins/ReadableByteStreamInternals.ts new file mode 100644 index 000000000..f44c385b4 --- /dev/null +++ b/src/js/builtins/ReadableByteStreamInternals.ts @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2016 Canon Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// @internal + +export function privateInitializeReadableByteStreamController(this, stream, underlyingByteSource, highWaterMark) { + if (!$isReadableStream(stream)) throw new TypeError("ReadableByteStreamController needs a ReadableStream"); + + // readableStreamController is initialized with null value. + if ($getByIdDirectPrivate(stream, "readableStreamController") !== null) + throw new TypeError("ReadableStream already has a controller"); + + $putByIdDirectPrivate(this, "controlledReadableStream", stream); + $putByIdDirectPrivate(this, "underlyingByteSource", underlyingByteSource); + $putByIdDirectPrivate(this, "pullAgain", false); + $putByIdDirectPrivate(this, "pulling", false); + $readableByteStreamControllerClearPendingPullIntos(this); + $putByIdDirectPrivate(this, "queue", $newQueue()); + $putByIdDirectPrivate(this, "started", 0); + $putByIdDirectPrivate(this, "closeRequested", false); + + let hwm = $toNumber(highWaterMark); + if (isNaN(hwm) || hwm < 0) throw new RangeError("highWaterMark value is negative or not a number"); + $putByIdDirectPrivate(this, "strategyHWM", hwm); + + let autoAllocateChunkSize = underlyingByteSource.autoAllocateChunkSize; + if (autoAllocateChunkSize !== undefined) { + autoAllocateChunkSize = $toNumber(autoAllocateChunkSize); + if (autoAllocateChunkSize <= 0 || autoAllocateChunkSize === Infinity || autoAllocateChunkSize === -Infinity) + throw new RangeError("autoAllocateChunkSize value is negative or equal to positive or negative infinity"); + } + $putByIdDirectPrivate(this, "autoAllocateChunkSize", autoAllocateChunkSize); + $putByIdDirectPrivate(this, "pendingPullIntos", $createFIFO()); + + const controller = this; + $promiseInvokeOrNoopNoCatch($getByIdDirectPrivate(controller, "underlyingByteSource"), "start", [controller]).$then( + () => { + $putByIdDirectPrivate(controller, "started", 1); + $assert(!$getByIdDirectPrivate(controller, "pulling")); + $assert(!$getByIdDirectPrivate(controller, "pullAgain")); + $readableByteStreamControllerCallPullIfNeeded(controller); + }, + error => { + if ($getByIdDirectPrivate(stream, "state") === $streamReadable) + $readableByteStreamControllerError(controller, error); + }, + ); + + $putByIdDirectPrivate(this, "cancel", $readableByteStreamControllerCancel); + $putByIdDirectPrivate(this, "pull", $readableByteStreamControllerPull); + + return this; +} + +export function readableStreamByteStreamControllerStart(this, controller) { + $putByIdDirectPrivate(controller, "start", undefined); +} + +export function privateInitializeReadableStreamBYOBRequest(this, controller, view) { + $putByIdDirectPrivate(this, "associatedReadableByteStreamController", controller); + $putByIdDirectPrivate(this, "view", view); +} + +export function isReadableByteStreamController(controller) { + // Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js). + // See corresponding function for explanations. + return $isObject(controller) && !!$getByIdDirectPrivate(controller, "underlyingByteSource"); +} + +export function isReadableStreamBYOBRequest(byobRequest) { + // Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js). + // See corresponding function for explanations. + return $isObject(byobRequest) && !!$getByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController"); +} + +export function isReadableStreamBYOBReader(reader) { + // Spec tells to return true only if reader has a readIntoRequests internal slot. + // However, since it is a private slot, it cannot be checked using hasOwnProperty(). + // Since readIntoRequests is initialized with an empty array, the following test is ok. + return $isObject(reader) && !!$getByIdDirectPrivate(reader, "readIntoRequests"); +} + +export function readableByteStreamControllerCancel(controller, reason) { + var pendingPullIntos = $getByIdDirectPrivate(controller, "pendingPullIntos"); + var first = pendingPullIntos.peek(); + if (first) first.bytesFilled = 0; + + $putByIdDirectPrivate(controller, "queue", $newQueue()); + return $promiseInvokeOrNoop($getByIdDirectPrivate(controller, "underlyingByteSource"), "cancel", [reason]); +} + +export function readableByteStreamControllerError(controller, e) { + $assert( + $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === $streamReadable, + ); + $readableByteStreamControllerClearPendingPullIntos(controller); + $putByIdDirectPrivate(controller, "queue", $newQueue()); + $readableStreamError($getByIdDirectPrivate(controller, "controlledReadableStream"), e); +} + +export function readableByteStreamControllerClose(controller) { + $assert(!$getByIdDirectPrivate(controller, "closeRequested")); + $assert( + $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === $streamReadable, + ); + + if ($getByIdDirectPrivate(controller, "queue").size > 0) { + $putByIdDirectPrivate(controller, "closeRequested", true); + return; + } + + var first = $getByIdDirectPrivate(controller, "pendingPullIntos")?.peek(); + if (first) { + if (first.bytesFilled > 0) { + const e = $makeTypeError("Close requested while there remain pending bytes"); + $readableByteStreamControllerError(controller, e); + throw e; + } + } + + $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream")); +} + +export function readableByteStreamControllerClearPendingPullIntos(controller) { + $readableByteStreamControllerInvalidateBYOBRequest(controller); + var existing = $getByIdDirectPrivate(controller, "pendingPullIntos"); + if (existing !== undefined) { + existing.clear(); + } else { + $putByIdDirectPrivate(controller, "pendingPullIntos", $createFIFO()); + } +} + +export function readableByteStreamControllerGetDesiredSize(controller) { + const stream = $getByIdDirectPrivate(controller, "controlledReadableStream"); + const state = $getByIdDirectPrivate(stream, "state"); + + if (state === $streamErrored) return null; + if (state === $streamClosed) return 0; + + return $getByIdDirectPrivate(controller, "strategyHWM") - $getByIdDirectPrivate(controller, "queue").size; +} + +export function readableStreamHasBYOBReader(stream) { + const reader = $getByIdDirectPrivate(stream, "reader"); + return reader !== undefined && $isReadableStreamBYOBReader(reader); +} + +export function readableStreamHasDefaultReader(stream) { + const reader = $getByIdDirectPrivate(stream, "reader"); + return reader !== undefined && $isReadableStreamDefaultReader(reader); +} + +export function readableByteStreamControllerHandleQueueDrain(controller) { + $assert( + $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === $streamReadable, + ); + if (!$getByIdDirectPrivate(controller, "queue").size && $getByIdDirectPrivate(controller, "closeRequested")) + $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream")); + else $readableByteStreamControllerCallPullIfNeeded(controller); +} + +export function readableByteStreamControllerPull(controller) { + const stream = $getByIdDirectPrivate(controller, "controlledReadableStream"); + $assert($readableStreamHasDefaultReader(stream)); + if ($getByIdDirectPrivate(controller, "queue").content?.isNotEmpty()) { + const entry = $getByIdDirectPrivate(controller, "queue").content.shift(); + $getByIdDirectPrivate(controller, "queue").size -= entry.byteLength; + $readableByteStreamControllerHandleQueueDrain(controller); + let view; + try { + view = new Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength); + } catch (error) { + return Promise.$reject(error); + } + return $createFulfilledPromise({ value: view, done: false }); + } + + if ($getByIdDirectPrivate(controller, "autoAllocateChunkSize") !== undefined) { + let buffer; + try { + buffer = $createUninitializedArrayBuffer($getByIdDirectPrivate(controller, "autoAllocateChunkSize")); + } catch (error) { + return Promise.$reject(error); + } + const pullIntoDescriptor = { + buffer, + byteOffset: 0, + byteLength: $getByIdDirectPrivate(controller, "autoAllocateChunkSize"), + bytesFilled: 0, + elementSize: 1, + ctor: Uint8Array, + readerType: "default", + }; + $getByIdDirectPrivate(controller, "pendingPullIntos").push(pullIntoDescriptor); + } + + const promise = $readableStreamAddReadRequest(stream); + $readableByteStreamControllerCallPullIfNeeded(controller); + return promise; +} + +export function readableByteStreamControllerShouldCallPull(controller) { + const stream = $getByIdDirectPrivate(controller, "controlledReadableStream"); + + if ($getByIdDirectPrivate(stream, "state") !== $streamReadable) return false; + if ($getByIdDirectPrivate(controller, "closeRequested")) return false; + if (!($getByIdDirectPrivate(controller, "started") > 0)) return false; + const reader = $getByIdDirectPrivate(stream, "reader"); + + if ( + reader && + ($getByIdDirectPrivate(reader, "readRequests")?.isNotEmpty() || !!$getByIdDirectPrivate(reader, "bunNativePtr")) + ) + return true; + if ( + $readableStreamHasBYOBReader(stream) && + $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readIntoRequests")?.isNotEmpty() + ) + return true; + if ($readableByteStreamControllerGetDesiredSize(controller) > 0) return true; + return false; +} + +export function readableByteStreamControllerCallPullIfNeeded(controller) { + if (!$readableByteStreamControllerShouldCallPull(controller)) return; + + if ($getByIdDirectPrivate(controller, "pulling")) { + $putByIdDirectPrivate(controller, "pullAgain", true); + return; + } + + $assert(!$getByIdDirectPrivate(controller, "pullAgain")); + $putByIdDirectPrivate(controller, "pulling", true); + $promiseInvokeOrNoop($getByIdDirectPrivate(controller, "underlyingByteSource"), "pull", [controller]).$then( + () => { + $putByIdDirectPrivate(controller, "pulling", false); + if ($getByIdDirectPrivate(controller, "pullAgain")) { + $putByIdDirectPrivate(controller, "pullAgain", false); + $readableByteStreamControllerCallPullIfNeeded(controller); + } + }, + error => { + if ( + $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === + $streamReadable + ) + $readableByteStreamControllerError(controller, error); + }, + ); +} + +export function transferBufferToCurrentRealm(buffer) { + // FIXME: Determine what should be done here exactly (what is already existing in current + // codebase and what has to be added). According to spec, Transfer operation should be + // performed in order to transfer buffer to current realm. For the moment, simply return + // received buffer. + return buffer; +} + +export function readableStreamReaderKind(reader) { + if (!!$getByIdDirectPrivate(reader, "readRequests")) return $getByIdDirectPrivate(reader, "bunNativePtr") ? 3 : 1; + + if (!!$getByIdDirectPrivate(reader, "readIntoRequests")) return 2; + + return 0; +} + +export function readableByteStreamControllerEnqueue(controller, chunk) { + const stream = $getByIdDirectPrivate(controller, "controlledReadableStream"); + $assert(!$getByIdDirectPrivate(controller, "closeRequested")); + $assert($getByIdDirectPrivate(stream, "state") === $streamReadable); + + switch ( + $getByIdDirectPrivate(stream, "reader") ? $readableStreamReaderKind($getByIdDirectPrivate(stream, "reader")) : 0 + ) { + /* default reader */ + case 1: { + if (!$getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests")?.isNotEmpty()) + $readableByteStreamControllerEnqueueChunk( + controller, + $transferBufferToCurrentRealm(chunk.buffer), + chunk.byteOffset, + chunk.byteLength, + ); + else { + $assert(!$getByIdDirectPrivate(controller, "queue").content.size()); + const transferredView = + chunk.constructor === Uint8Array ? chunk : new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength); + $readableStreamFulfillReadRequest(stream, transferredView, false); + } + break; + } + + /* BYOB */ + case 2: { + $readableByteStreamControllerEnqueueChunk( + controller, + $transferBufferToCurrentRealm(chunk.buffer), + chunk.byteOffset, + chunk.byteLength, + ); + $readableByteStreamControllerProcessPullDescriptors(controller); + break; + } + + /* NativeReader */ + case 3: { + // reader.$enqueueNative($getByIdDirectPrivate(reader, "bunNativePtr"), chunk); + + break; + } + + default: { + $assert(!$isReadableStreamLocked(stream)); + $readableByteStreamControllerEnqueueChunk( + controller, + $transferBufferToCurrentRealm(chunk.buffer), + chunk.byteOffset, + chunk.byteLength, + ); + break; + } + } +} + +// Spec name: readableByteStreamControllerEnqueueChunkToQueue. +export function readableByteStreamControllerEnqueueChunk(controller, buffer, byteOffset, byteLength) { + $getByIdDirectPrivate(controller, "queue").content.push({ + buffer: buffer, + byteOffset: byteOffset, + byteLength: byteLength, + }); + $getByIdDirectPrivate(controller, "queue").size += byteLength; +} + +export function readableByteStreamControllerRespondWithNewView(controller, view) { + $assert($getByIdDirectPrivate(controller, "pendingPullIntos").isNotEmpty()); + + let firstDescriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").peek(); + + if (firstDescriptor.byteOffset + firstDescriptor.bytesFilled !== view.byteOffset) + throw new RangeError("Invalid value for view.byteOffset"); + + if (firstDescriptor.byteLength !== view.byteLength) throw new RangeError("Invalid value for view.byteLength"); + + firstDescriptor.buffer = view.buffer; + $readableByteStreamControllerRespondInternal(controller, view.byteLength); +} + +export function readableByteStreamControllerRespond(controller, bytesWritten) { + bytesWritten = $toNumber(bytesWritten); + + if (isNaN(bytesWritten) || bytesWritten === Infinity || bytesWritten < 0) + throw new RangeError("bytesWritten has an incorrect value"); + + $assert($getByIdDirectPrivate(controller, "pendingPullIntos").isNotEmpty()); + + $readableByteStreamControllerRespondInternal(controller, bytesWritten); +} + +export function readableByteStreamControllerRespondInternal(controller, bytesWritten) { + let firstDescriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").peek(); + let stream = $getByIdDirectPrivate(controller, "controlledReadableStream"); + + if ($getByIdDirectPrivate(stream, "state") === $streamClosed) { + if (bytesWritten !== 0) throw new TypeError("bytesWritten is different from 0 even though stream is closed"); + $readableByteStreamControllerRespondInClosedState(controller, firstDescriptor); + } else { + $assert($getByIdDirectPrivate(stream, "state") === $streamReadable); + $readableByteStreamControllerRespondInReadableState(controller, bytesWritten, firstDescriptor); + } +} + +export function readableByteStreamControllerRespondInReadableState(controller, bytesWritten, pullIntoDescriptor) { + if (pullIntoDescriptor.bytesFilled + bytesWritten > pullIntoDescriptor.byteLength) + throw new RangeError("bytesWritten value is too great"); + + $assert( + $getByIdDirectPrivate(controller, "pendingPullIntos").isEmpty() || + $getByIdDirectPrivate(controller, "pendingPullIntos").peek() === pullIntoDescriptor, + ); + $readableByteStreamControllerInvalidateBYOBRequest(controller); + pullIntoDescriptor.bytesFilled += bytesWritten; + + if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) return; + + $readableByteStreamControllerShiftPendingDescriptor(controller); + const remainderSize = pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize; + + if (remainderSize > 0) { + const end = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled; + const remainder = $cloneArrayBuffer(pullIntoDescriptor.buffer, end - remainderSize, remainderSize); + $readableByteStreamControllerEnqueueChunk(controller, remainder, 0, remainder.byteLength); + } + + pullIntoDescriptor.buffer = $transferBufferToCurrentRealm(pullIntoDescriptor.buffer); + pullIntoDescriptor.bytesFilled -= remainderSize; + $readableByteStreamControllerCommitDescriptor( + $getByIdDirectPrivate(controller, "controlledReadableStream"), + pullIntoDescriptor, + ); + $readableByteStreamControllerProcessPullDescriptors(controller); +} + +export function readableByteStreamControllerRespondInClosedState(controller, firstDescriptor) { + firstDescriptor.buffer = $transferBufferToCurrentRealm(firstDescriptor.buffer); + $assert(firstDescriptor.bytesFilled === 0); + + if ($readableStreamHasBYOBReader($getByIdDirectPrivate(controller, "controlledReadableStream"))) { + while ( + $getByIdDirectPrivate( + $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "reader"), + "readIntoRequests", + )?.isNotEmpty() + ) { + let pullIntoDescriptor = $readableByteStreamControllerShiftPendingDescriptor(controller); + $readableByteStreamControllerCommitDescriptor( + $getByIdDirectPrivate(controller, "controlledReadableStream"), + pullIntoDescriptor, + ); + } + } +} + +// Spec name: readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue (shortened for readability). +export function readableByteStreamControllerProcessPullDescriptors(controller) { + $assert(!$getByIdDirectPrivate(controller, "closeRequested")); + while ($getByIdDirectPrivate(controller, "pendingPullIntos").isNotEmpty()) { + if ($getByIdDirectPrivate(controller, "queue").size === 0) return; + let pullIntoDescriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").peek(); + if ($readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor)) { + $readableByteStreamControllerShiftPendingDescriptor(controller); + $readableByteStreamControllerCommitDescriptor( + $getByIdDirectPrivate(controller, "controlledReadableStream"), + pullIntoDescriptor, + ); + } + } +} + +// Spec name: readableByteStreamControllerFillPullIntoDescriptorFromQueue (shortened for readability). +export function readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor) { + const currentAlignedBytes = + pullIntoDescriptor.bytesFilled - (pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize); + const maxBytesToCopy = + $getByIdDirectPrivate(controller, "queue").size < pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled + ? $getByIdDirectPrivate(controller, "queue").size + : pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled; + const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; + const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % pullIntoDescriptor.elementSize); + let totalBytesToCopyRemaining = maxBytesToCopy; + let ready = false; + + if (maxAlignedBytes > currentAlignedBytes) { + totalBytesToCopyRemaining = maxAlignedBytes - pullIntoDescriptor.bytesFilled; + ready = true; + } + + while (totalBytesToCopyRemaining > 0) { + let headOfQueue = $getByIdDirectPrivate(controller, "queue").content.peek(); + const bytesToCopy = + totalBytesToCopyRemaining < headOfQueue.byteLength ? totalBytesToCopyRemaining : headOfQueue.byteLength; + // Copy appropriate part of pullIntoDescriptor.buffer to headOfQueue.buffer. + // Remark: this implementation is not completely aligned on the definition of CopyDataBlockBytes + // operation of ECMAScript (the case of Shared Data Block is not considered here, but it doesn't seem to be an issue). + const destStart = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled; + // FIXME: As indicated in comments of bug 172717, access to set is not safe. However, using prototype.$set.$call does + // not work ($set is undefined). A safe way to do that is needed. + new Uint8Array(pullIntoDescriptor.buffer).set( + new Uint8Array(headOfQueue.buffer, headOfQueue.byteOffset, bytesToCopy), + destStart, + ); + + if (headOfQueue.byteLength === bytesToCopy) $getByIdDirectPrivate(controller, "queue").content.shift(); + else { + headOfQueue.byteOffset += bytesToCopy; + headOfQueue.byteLength -= bytesToCopy; + } + + $getByIdDirectPrivate(controller, "queue").size -= bytesToCopy; + $assert( + $getByIdDirectPrivate(controller, "pendingPullIntos").isEmpty() || + $getByIdDirectPrivate(controller, "pendingPullIntos").peek() === pullIntoDescriptor, + ); + $readableByteStreamControllerInvalidateBYOBRequest(controller); + pullIntoDescriptor.bytesFilled += bytesToCopy; + totalBytesToCopyRemaining -= bytesToCopy; + } + + if (!ready) { + $assert($getByIdDirectPrivate(controller, "queue").size === 0); + $assert(pullIntoDescriptor.bytesFilled > 0); + $assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); + } + + return ready; +} + +// Spec name: readableByteStreamControllerShiftPendingPullInto (renamed for consistency). +export function readableByteStreamControllerShiftPendingDescriptor(controller) { + let descriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").shift(); + $readableByteStreamControllerInvalidateBYOBRequest(controller); + return descriptor; +} + +export function readableByteStreamControllerInvalidateBYOBRequest(controller) { + if ($getByIdDirectPrivate(controller, "byobRequest") === undefined) return; + const byobRequest = $getByIdDirectPrivate(controller, "byobRequest"); + $putByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController", undefined); + $putByIdDirectPrivate(byobRequest, "view", undefined); + $putByIdDirectPrivate(controller, "byobRequest", undefined); +} + +// Spec name: readableByteStreamControllerCommitPullIntoDescriptor (shortened for readability). +export function readableByteStreamControllerCommitDescriptor(stream, pullIntoDescriptor) { + $assert($getByIdDirectPrivate(stream, "state") !== $streamErrored); + let done = false; + if ($getByIdDirectPrivate(stream, "state") === $streamClosed) { + $assert(!pullIntoDescriptor.bytesFilled); + done = true; + } + let filledView = $readableByteStreamControllerConvertDescriptor(pullIntoDescriptor); + if (pullIntoDescriptor.readerType === "default") $readableStreamFulfillReadRequest(stream, filledView, done); + else { + $assert(pullIntoDescriptor.readerType === "byob"); + $readableStreamFulfillReadIntoRequest(stream, filledView, done); + } +} + +// Spec name: readableByteStreamControllerConvertPullIntoDescriptor (shortened for readability). +export function readableByteStreamControllerConvertDescriptor(pullIntoDescriptor) { + $assert(pullIntoDescriptor.bytesFilled <= pullIntoDescriptor.byteLength); + $assert(pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize === 0); + + return new pullIntoDescriptor.ctor( + pullIntoDescriptor.buffer, + pullIntoDescriptor.byteOffset, + pullIntoDescriptor.bytesFilled / pullIntoDescriptor.elementSize, + ); +} + +export function readableStreamFulfillReadIntoRequest(stream, chunk, done) { + const readIntoRequest = $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readIntoRequests").shift(); + $fulfillPromise(readIntoRequest, { value: chunk, done: done }); +} + +export function readableStreamBYOBReaderRead(reader, view) { + const stream = $getByIdDirectPrivate(reader, "ownerReadableStream"); + $assert(!!stream); + + $putByIdDirectPrivate(stream, "disturbed", true); + if ($getByIdDirectPrivate(stream, "state") === $streamErrored) + return Promise.$reject($getByIdDirectPrivate(stream, "storedError")); + + return $readableByteStreamControllerPullInto($getByIdDirectPrivate(stream, "readableStreamController"), view); +} + +export function readableByteStreamControllerPullInto(controller, view) { + const stream = $getByIdDirectPrivate(controller, "controlledReadableStream"); + let elementSize = 1; + // Spec describes that in the case where view is a TypedArray, elementSize + // should be set to the size of an element (e.g. 2 for UInt16Array). For + // DataView, BYTES_PER_ELEMENT is undefined, contrary to the same property + // for TypedArrays. + // FIXME: Getting BYTES_PER_ELEMENT like this is not safe (property is read-only + // but can be modified if the prototype is redefined). A safe way of getting + // it would be to determine which type of ArrayBufferView view is an instance + // of based on typed arrays private variables. However, this is not possible due + // to bug 167697, which prevents access to typed arrays through their private + // names unless public name has already been met before. + if (view.BYTES_PER_ELEMENT !== undefined) elementSize = view.BYTES_PER_ELEMENT; + + // FIXME: Getting constructor like this is not safe. A safe way of getting + // it would be to determine which type of ArrayBufferView view is an instance + // of, and to assign appropriate constructor based on this (e.g. ctor = + // $Uint8Array). However, this is not possible due to bug 167697, which + // prevents access to typed arrays through their private names unless public + // name has already been met before. + const ctor = view.constructor; + + const pullIntoDescriptor = { + buffer: view.buffer, + byteOffset: view.byteOffset, + byteLength: view.byteLength, + bytesFilled: 0, + elementSize, + ctor, + readerType: "byob", + }; + + var pending = $getByIdDirectPrivate(controller, "pendingPullIntos"); + if (pending?.isNotEmpty()) { + pullIntoDescriptor.buffer = $transferBufferToCurrentRealm(pullIntoDescriptor.buffer); + pending.push(pullIntoDescriptor); + return $readableStreamAddReadIntoRequest(stream); + } + + if ($getByIdDirectPrivate(stream, "state") === $streamClosed) { + const emptyView = new ctor(pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, 0); + return $createFulfilledPromise({ value: emptyView, done: true }); + } + + if ($getByIdDirectPrivate(controller, "queue").size > 0) { + if ($readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor)) { + const filledView = $readableByteStreamControllerConvertDescriptor(pullIntoDescriptor); + $readableByteStreamControllerHandleQueueDrain(controller); + return $createFulfilledPromise({ value: filledView, done: false }); + } + if ($getByIdDirectPrivate(controller, "closeRequested")) { + const e = $makeTypeError("Closing stream has been requested"); + $readableByteStreamControllerError(controller, e); + return Promise.$reject(e); + } + } + + pullIntoDescriptor.buffer = $transferBufferToCurrentRealm(pullIntoDescriptor.buffer); + $getByIdDirectPrivate(controller, "pendingPullIntos").push(pullIntoDescriptor); + const promise = $readableStreamAddReadIntoRequest(stream); + $readableByteStreamControllerCallPullIfNeeded(controller); + return promise; +} + +export function readableStreamAddReadIntoRequest(stream) { + $assert($isReadableStreamBYOBReader($getByIdDirectPrivate(stream, "reader"))); + $assert( + $getByIdDirectPrivate(stream, "state") === $streamReadable || + $getByIdDirectPrivate(stream, "state") === $streamClosed, + ); + + const readRequest = $newPromise(); + $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readIntoRequests").push(readRequest); + + return readRequest; +} |