/* * 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 function privateInitializeReadableByteStreamController(stream, underlyingByteSource, highWaterMark) { "use strict"; if (!@isReadableStream(stream)) @throwTypeError("ReadableByteStreamController needs a ReadableStream"); // readableStreamController is initialized with null value. if (@getByIdDirectPrivate(stream, "readableStreamController") !== null) @throwTypeError("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", false); @putByIdDirectPrivate(this, "closeRequested", false); let hwm = @toNumber(highWaterMark); if (@isNaN(hwm) || hwm < 0) @throwRangeError("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) @throwRangeError("autoAllocateChunkSize value is negative or equal to positive or negative infinity"); } @putByIdDirectPrivate(this, "autoAllocateChunkSize", autoAllocateChunkSize); @putByIdDirectPrivate(this, "pendingPullIntos", []); const controller = this; const startResult = @promiseInvokeOrNoopNoCatch(underlyingByteSource, "start", [this]).@then(() => { @putByIdDirectPrivate(controller, "started", true); @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; } function privateInitializeReadableStreamBYOBRequest(controller, view) { "use strict"; @putByIdDirectPrivate(this, "associatedReadableByteStreamController", controller); @putByIdDirectPrivate(this, "view", view); } function isReadableByteStreamController(controller) { "use strict"; // Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js). // See corresponding function for explanations. return @isObject(controller) && !!@getByIdDirectPrivate(controller, "underlyingByteSource"); } function isReadableStreamBYOBRequest(byobRequest) { "use strict"; // Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js). // See corresponding function for explanations. return @isObject(byobRequest) && !!@getByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController"); } function isReadableStreamBYOBReader(reader) { "use strict"; // 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"); } function readableByteStreamControllerCancel(controller, reason) { "use strict"; var pendingPullIntos = @getByIdDirectPrivate(controller, "pendingPullIntos"); if (pendingPullIntos.length > 0) pendingPullIntos[0].bytesFilled = 0; @putByIdDirectPrivate(controller, "queue", @newQueue()); return @promiseInvokeOrNoop(@getByIdDirectPrivate(controller, "underlyingByteSource"), "cancel", [reason]); } function readableByteStreamControllerError(controller, e) { "use strict"; @assert(@getByIdDirectPrivate(@getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === @streamReadable); @readableByteStreamControllerClearPendingPullIntos(controller); @putByIdDirectPrivate(controller, "queue", @newQueue()); @readableStreamError(@getByIdDirectPrivate(controller, "controlledReadableStream"), e); } function readableByteStreamControllerClose(controller) { "use strict"; @assert(!@getByIdDirectPrivate(controller, "closeRequested")); @assert(@getByIdDirectPrivate(@getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === @streamReadable); if (@getByIdDirectPrivate(controller, "queue").size > 0) { @putByIdDirectPrivate(controller, "closeRequested", true); return; } var pendingPullIntos = @getByIdDirectPrivate(controller, "pendingPullIntos"); if (pendingPullIntos.length > 0) { if (pendingPullIntos[0].bytesFilled > 0) { const e = @makeTypeError("Close requested while there remain pending bytes"); @readableByteStreamControllerError(controller, e); throw e; } } @readableStreamClose(@getByIdDirectPrivate(controller, "controlledReadableStream")); } function readableByteStreamControllerClearPendingPullIntos(controller) { "use strict"; @readableByteStreamControllerInvalidateBYOBRequest(controller); @putByIdDirectPrivate(controller, "pendingPullIntos", []); } function readableByteStreamControllerGetDesiredSize(controller) { "use strict"; 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; } function readableStreamHasBYOBReader(stream) { "use strict"; const reader = @getByIdDirectPrivate(stream, "reader"); return reader !== @undefined && @isReadableStreamBYOBReader(reader); } function readableStreamHasDefaultReader(stream) { "use strict"; const reader = @getByIdDirectPrivate(stream, "reader"); return reader !== @undefined && @isReadableStreamDefaultReader(reader); } function readableByteStreamControllerHandleQueueDrain(controller) { "use strict"; @assert(@getByIdDirectPrivate(@getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === @streamReadable); if (!@getByIdDirectPrivate(controller, "queue").size && @getByIdDirectPrivate(controller, "closeRequested")) @readableStreamClose(@getByIdDirectPrivate(controller, "controlledReadableStream")); else @readableByteStreamControllerCallPullIfNeeded(controller); } function readableByteStreamControllerPull(controller) { "use strict"; const stream = @getByIdDirectPrivate(controller, "controlledReadableStream"); @assert(@readableStreamHasDefaultReader(stream)); if (@getByIdDirectPrivate(controller, "queue").size > 0) { @assert(@getByIdDirectPrivate(@getByIdDirectPrivate(stream, "reader"), "readRequests").length === 0); 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 = new @ArrayBuffer(@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; } function readableByteStreamControllerShouldCallPull(controller) { "use strict"; const stream = @getByIdDirectPrivate(controller, "controlledReadableStream"); if (@getByIdDirectPrivate(stream, "state") !== @streamReadable) return false; if (@getByIdDirectPrivate(controller, "closeRequested")) return false; if (!@getByIdDirectPrivate(controller, "started")) return false; if (@readableStreamHasDefaultReader(stream) && @getByIdDirectPrivate(@getByIdDirectPrivate(stream, "reader"), "readRequests").length > 0) return true; if (@readableStreamHasBYOBReader(stream) && @getByIdDirectPrivate(@getByIdDirectPrivate(stream, "reader"), "readIntoRequests").length > 0) return true; if (@readableByteStreamControllerGetDesiredSize(controller) > 0) return true; return false; } function readableByteStreamControllerCallPullIfNeeded(controller) { "use strict"; 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); }); } function transferBufferToCurrentRealm(buffer) { "use strict"; // 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; } function readableByteStreamControllerEnqueue(controller, chunk) { "use strict"; const stream = @getByIdDirectPrivate(controller, "controlledReadableStream"); @assert(!@getByIdDirectPrivate(controller, "closeRequested")); @assert(@getByIdDirectPrivate(stream, "state") === @streamReadable); const buffer = chunk.buffer; const byteOffset = chunk.byteOffset; const byteLength = chunk.byteLength; const transferredBuffer = @transferBufferToCurrentRealm(buffer); if (@readableStreamHasDefaultReader(stream)) { if (!@getByIdDirectPrivate(@getByIdDirectPrivate(stream, "reader"), "readRequests").length) @readableByteStreamControllerEnqueueChunk(controller, transferredBuffer, byteOffset, byteLength); else { @assert(!@getByIdDirectPrivate(controller, "queue").content.length); let transferredView = new @Uint8Array(transferredBuffer, byteOffset, byteLength); @readableStreamFulfillReadRequest(stream, transferredView, false); } return; } if (@readableStreamHasBYOBReader(stream)) { @readableByteStreamControllerEnqueueChunk(controller, transferredBuffer, byteOffset, byteLength); @readableByteStreamControllerProcessPullDescriptors(controller); return; } @assert(!@isReadableStreamLocked(stream)); @readableByteStreamControllerEnqueueChunk(controller, transferredBuffer, byteOffset, byteLength); } // Spec name: readableByteStreamControllerEnqueueChunkToQueue. function readableByteStreamControllerEnqueueChunk(controller, buffer, byteOffset, byteLength) { "use strict"; @getByIdDirectPrivate(controller, "queue").content.@push({ buffer: buffer, byteOffset: byteOffset, byteLength: byteLength }); @getByIdDirectPrivate(controller, "queue").size += byteLength; } function readableByteStreamControllerRespondWithNewView(controller, view) { "use strict"; @assert(@getByIdDirectPrivate(controller, "pendingPullIntos").length > 0); let firstDescriptor = @getByIdDirectPrivate(controller, "pendingPullIntos")[0]; if (firstDescriptor.byteOffset + firstDescriptor.bytesFilled !== view.byteOffset) @throwRangeError("Invalid value for view.byteOffset"); if (firstDescriptor.byteLength !== view.byteLength) @throwRangeError("Invalid value for view.byteLength"); firstDescriptor.buffer = view.buffer; @readableByteStreamControllerRespondInternal(controller, view.byteLength); } function readableByteStreamControllerRespond(controller, bytesWritten) { "use strict"; bytesWritten = @toNumber(bytesWritten); if (@isNaN(bytesWritten) || bytesWritten === @Infinity || bytesWritten < 0 ) @throwRangeError("bytesWritten has an incorrect value"); @assert(@getByIdDirectPrivate(controller, "pendingPullIntos").length > 0); @readableByteStreamControllerRespondInternal(controller, bytesWritten); } function readableByteStreamControllerRespondInternal(controller, bytesWritten) { "use strict"; let firstDescriptor = @getByIdDirectPrivate(controller, "pendingPullIntos")[0]; let stream = @getByIdDirectPrivate(controller, "controlledReadableStream"); if (@getByIdDirectPrivate(stream, "state") === @streamClosed) { if (bytesWritten !== 0) @throwTypeError("bytesWritten is different from 0 even though stream is closed"); @readableByteStreamControllerRespondInClosedState(controller, firstDescriptor); } else { @assert(@getByIdDirectPrivate(stream, "state") === @streamReadable); @readableByteStreamControllerRespondInReadableState(controller, bytesWritten, firstDescriptor); } } function readableByteStreamControllerRespondInReadableState(controller, bytesWritten, pullIntoDescriptor) { "use strict"; if (pullIntoDescriptor.bytesFilled + bytesWritten > pullIntoDescriptor.byteLength) @throwRangeError("bytesWritten value is too great"); @assert(@getByIdDirectPrivate(controller, "pendingPullIntos").length === 0 || @getByIdDirectPrivate(controller, "pendingPullIntos")[0] === 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); } function readableByteStreamControllerRespondInClosedState(controller, firstDescriptor) { "use strict"; firstDescriptor.buffer = @transferBufferToCurrentRealm(firstDescriptor.buffer); @assert(firstDescriptor.bytesFilled === 0); if (@readableStreamHasBYOBReader(@getByIdDirectPrivate(controller, "controlledReadableStream"))) { while (@getByIdDirectPrivate(@getByIdDirectPrivate(@getByIdDirectPrivate(controller, "controlledReadableStream"), "reader"), "readIntoRequests").length > 0) { let pullIntoDescriptor = @readableByteStreamControllerShiftPendingDescriptor(controller); @readableByteStreamControllerCommitDescriptor(@getByIdDirectPrivate(controller, "controlledReadableStream"), pullIntoDescriptor); } } } // Spec name: readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue (shortened for readability). function readableByteStreamControllerProcessPullDescriptors(controller) { "use strict"; @assert(!@getByIdDirectPrivate(controller, "closeRequested")); while (@getByIdDirectPrivate(controller, "pendingPullIntos").length > 0) { if (@getByIdDirectPrivate(controller, "queue").size === 0) return; let pullIntoDescriptor = @getByIdDirectPrivate(controller, "pendingPullIntos")[0]; if (@readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor)) { @readableByteStreamControllerShiftPendingDescriptor(controller); @readableByteStreamControllerCommitDescriptor(@getByIdDirectPrivate(controller, "controlledReadableStream"), pullIntoDescriptor); } } } // Spec name: readableByteStreamControllerFillPullIntoDescriptorFromQueue (shortened for readability). function readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor) { "use strict"; 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[0]; 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").length === 0 || @getByIdDirectPrivate(controller, "pendingPullIntos")[0] === 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). function readableByteStreamControllerShiftPendingDescriptor(controller) { "use strict"; let descriptor = @getByIdDirectPrivate(controller, "pendingPullIntos").@shift(); @readableByteStreamControllerInvalidateBYOBRequest(controller); return descriptor; } function readableByteStreamControllerInvalidateBYOBRequest(controller) { "use strict"; 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). function readableByteStreamControllerCommitDescriptor(stream, pullIntoDescriptor) { "use strict"; @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). function readableByteStreamControllerConvertDescriptor(pullIntoDescriptor) { "use strict"; @assert(pullIntoDescriptor.bytesFilled <= pullIntoDescriptor.byteLength); @assert(pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize === 0); return new pullIntoDescriptor.ctor(pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, pullIntoDescriptor.bytesFilled / pullIntoDescriptor.elementSize); } function readableStreamFulfillReadIntoRequest(stream, chunk, done) { "use strict"; const readIntoRequest = @getByIdDirectPrivate(@getByIdDirectPrivate(stream, "reader"), "readIntoRequests").@shift(); @fulfillPromise(readIntoRequest, { value: chunk, done: done }); } function readableStreamBYOBReaderRead(reader, view) { "use strict"; 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); } function readableByteStreamControllerPullInto(controller, view) { "use strict"; 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' }; if (@getByIdDirectPrivate(controller, "pendingPullIntos").length) { pullIntoDescriptor.buffer = @transferBufferToCurrentRealm(pullIntoDescriptor.buffer); @getByIdDirectPrivate(controller, "pendingPullIntos").@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; } function readableStreamAddReadIntoRequest(stream) { "use strict"; @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; }